Creating a 3D tagcloud in Silverlight (part 1)

19 comments

When I saw the wp-cumulus plugin by Roy Tanck, I thought it would be a great idea to implement the same sort of functionality in Silverlight. It’s hardly original but allows me to learn some parts of the Silverlight framework.

The components behind it are quite simple:

  • Get (or send) the tags from your HTML page to the Silverlight usercontrol
  • Render the tags so it looks 3D
  • Create a method to rotate the tags based on the position of your mouse

Choosing a 3D library

The current version of Silverlight doesn’t include 3D functionality like WPF does through the Media3D namespace. Fortunately some developers implemented the same functionality in libraries for Silverlight. The main options I found were Kit3D and Axelerate3D. I decided to use the last one because that one mimics the RotateTransform3D class in WPF 3D the best (it contains a TryTransform method).

Rendering the tags

I decided to tackle the second item first, because if I wasn’t able to manage this, the other items wouldn’t be very useful.

To create a tag in 3D you need some basic functionality:

  • A way to store it’s x, y and z coordinates
  • A hyperlinkbutton to redirect to a page that shows all the items with that tag
  • A textblock to display the tag
public class Tag3D
{
public Tag3D(double x, double y, double z, string text)
{
centerPoint = new Point3D(x, y, z);
textBlock = new TextBlock();
textBlock.Text = text;
btnLink = new HyperlinkButton();
btnLink.Content = textBlock;
}

public HyperlinkButton btnLink { get; set; }

public TextBlock textBlock { get; set; }

public Point3D centerPoint { get; set; }

}

Then we need a way to make it look like it’s rendered in 3D. We do that by changing the fontsize and the opacity of the text. For that I created a method Redraw:

public void Redraw(double xOffset, double yOffset)
{
double posZ = centerPoint.Z + 200;
btnLink.FontSize = 10 * (posZ / 100);
double alpha = centerPoint.Z + 200;
if (alpha > 255)
alpha = 255;

if (alpha < 0)
alpha = 0;

btnLink.Foreground = new SolidColorBrush(Color.FromArgb(Convert.ToByte(alpha), 0, 0, ));
Canvas.SetLeft(btnLink, centerPoint.X + xOffset – (btnLink.ActualWidth / 2));
Canvas.SetTop(btnLink, -centerPoint.Y + yOffset – (btnLink.ActualHeight/ 2));
Canvas.SetZIndex(btnLink, Convert.ToInt32(centerPoint.Z));
}
Placing the tags

To distribute the tags evenly over the sphere, we need some math. Luckily someone was way ahead of me and posted a useful blogentry on this subject (this technique is also used in the wp-cumulus plugin).

The following method creates and places the tags in the canvas:

private void FillTags()
{
tagBlocks = new List<tag3D>();

string[] tags = new string[] { “Silverlight”,
“WPF”,
“3D”,
“Rotation”,
“SharePoint”,
“.Net”,
“C#”,
“Transform”,
“Blog”,
“TagCloud”,
“Tam Tam”,
“Axelerate3D”,
“MOSS”,
“Math”};

double radius = RootCanvas.Width / 3;
int max = tags.Length;
double phi = 0;
double theta = 0;

for (int i = 1; i < max + 1; i++)
{
phi = Math.Acos(-1.0 + (2.0 * i – 1.0) / max);
theta = Math.Sqrt(max * Math.PI) * phi;
double x = radius * Math.Cos(theta) * Math.Sin(phi);
double y = radius * Math.Sin(theta) * Math.Sin(phi);
double z = radius * Math.Cos(phi);

Tag3D tag = new Tag3D(x, y, z, tags[i -1]);
tag.Redraw(RootCanvas.Width / 2, RootCanvas.Height / 2);
RootCanvas.Children.Add(tag.btnLink);
tagBlocks.Add(tag);
}
}

At the moment the tags to render are hard-coded but we’ll sort that out in part 2.

Rotating the tags

To rotate the tags we will use the position of the mouse as a starting point. When the mousepointer is in the center the tagcloud will remain in the current position. Once the mouse is further away from the centerpoint we’ll increase the rotationspeed. The location of the mousepointer compared to the centerpoint will set the angle of the rotation.

First we will set the rotation when the tagcloud loads:

void TagCloud_Loaded(object sender, RoutedEventArgs e)
{
FillTags();
rotateTransform = new RotateTransform3D();
rotateTransform.Rotation = new AxisAngleRotation3D(new Vector3D(1.0, 0.0, 0.0), 0);
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
LayoutRoot.MouseEnter += new MouseEventHandler(LayoutRoot_MouseEnter);
LayoutRoot.MouseLeave += new MouseEventHandler(LayoutRoot_MouseLeave);
}

Here we set the rotation angle to 0 and the rotationaxis to the x-axis. When the mouse moves, we’ll change those parameters, so the rotation will have an effect:

void LayoutRoot_MouseMove(object sender, MouseEventArgs e)
{
Point mouseLocation = e.GetPosition(RootCanvas);
double relativeX = mouseLocation.X – (RootCanvas.ActualWidth / 2);
double relativeY = mouseLocation.Y – (RootCanvas.ActualHeight / 2);
MouseX.Text = relativeX.ToString();
MouseY.Text = relativeY.ToString();
double speed = Math.Sqrt(Math.Pow(relativeX, 2) + Math.Pow(relativeY, 2)) / 170;
RotationSpeed.Text = speed.ToString();
rotateTransform.Rotation = new AxisAngleRotation3D(new Vector3D(relativeY, relativeX, 0), speed);
}

To trigger the movement, we have to capture the MouseEnter and MouseLeave events:

void LayoutRoot_MouseLeave(object sender, MouseEventArgs e)
{
     LayoutRoot.MouseMove -= LayoutRoot_MouseMove;
     runRotation = false;
}

void LayoutRoot_MouseEnter(object sender, MouseEventArgs e)
{
     LayoutRoot.MouseMove += new MouseEventHandler(LayoutRoot_MouseMove);
     runRotation = true;
}

Now that the rotationparameters are set we need to rotate the tags, or more precisely the centerpoint of the tag. To accomplish this we’ll make use of the Rendering event of the CompositionTarget object. This is called everytime the Silverlight plugin wants to render a new frame.

void CompositionTarget_Rendering(object sender, EventArgs e)
{
    if (runRotation)
    {
        if (((AxisAngleRotation3D)rotateTransform.Rotation).Angle > 0.05)
        RotateBlocks();
    }
}

private void RotateBlocks()
{
foreach (Tag3D textBlock in tagBlocks)
{
Point3D newPoint;
if (rotateTransform.TryTransform(textBlock.centerPoint, out newPoint))
{
textBlock.centerPoint = newPoint;
textBlock.Redraw(RootCanvas.ActualWidth / 2, RootCanvas.ActualHeight / 2);
}
}
}

To relieve the CPU a bit, we’ll only rotate the tags if the rotation angle is higher than a threshold value. The actual transformation is accomplished by invoking the TryTransform method and passing it the current centerpoint of each tag.

At the moment the Silverlight control looks like this:

In the next part I’ll show you a way to dynamically set the tags, base their fontsize on the actual weight of the tag and actually use the hyperlink button.

Related posts:

  1. Creating a 3D tagcloud in Silverlight (part 2)
  2. Hosting your Silverlight application and media in the cloud
  3. Silverlight: Multiple animations on one property through Transforms
  4. SP2010 Installation – Error creating configuration database
Tagged , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

18 Comments

  1. Bart
    Posted January 20, 2010 at 7:57 pm | Permalink

    Heel cool, goed werk.

  2. Pascal
    Posted January 20, 2010 at 7:58 pm | Permalink

    Really a great tutorial Peter ! I have recently installed the wp-cumulus plugin in our blog and I really like it. Your control is really nice too and I’d like to use it. I can compile and it works fine, but I have a question since I’m not familiar with 3Dvector. I’ve not played with it much tonight because 1AM and I’m tired.. heh but I would like to modify it to make it spin the other way when my mouse is in the center and I move it in the upper left corner and same from center to bottom right. Center to both bottom left and upper right is working fine. Not sure if my clear, take a look at our tag cloud on http://blog.analystik.ca

  3. Peter
    Posted January 20, 2010 at 7:58 pm | Permalink

    @Pacal

    I’m not sure what the values need to be, but if you change the sign of the relativeX, relativeY and speed (that’s the rotation angle around the rotation axis) in the mouseMove handler, you should be able to get what you need. It’s only 8 combinations to try.

  4. Sagar
    Posted January 20, 2010 at 7:58 pm | Permalink

    hi,tutorial is very good , but i m facing some problems on how exactly you set rotation angle based upon the mouse position ,
    rotateTransform.Rotation = new AxisAngleRotation3D(new Vector3D(relativeY, relativeX, 0), speed);
    can you please elaborate on that?

  5. Peter
    Posted January 20, 2010 at 7:58 pm | Permalink

    The actual angle of the rotation is based on the distance of the mousepointer to the center of the sphere. The rotationaxis is in a right angle to the line you can trace from the mousepointer to the center. For the rest, it’s basic vector math.

  6. Sagar Sumant
    Posted January 20, 2010 at 7:58 pm | Permalink

    Hi ,just few more questions
    1)How do you calculate angle of rotation?In rotateTransform.Rotation we are storing new Vector3D(relativeY, relativeX, 0), speed),
    2)rotateTransform.TryTransform(textBlock.centerPoint, out newPoint) ,whats this TryTransform function ,how do we implement it,or are you using the any of the libraries you mentioned above ,i.e., Kit3D and Axelerate3D.
    if you dont have time ,please point out me to some links if you have ,thanks in advance.

  7. Peter
    Posted January 20, 2010 at 7:58 pm | Permalink

    @Sagar

    The speed is calculated by using the distance from the mousepointer to the centerpoint.

    The TryTransform function comes from Axelerate3D

  8. Mark Monster
    Posted January 20, 2010 at 7:58 pm | Permalink

    Very good work. Haven’t seen a 3d tagcloud running in Silverlight yet.

    Did you submit your article to Silverlight-Cream? I think this article deserves more attention!

  9. Peter
    Posted January 20, 2010 at 7:58 pm | Permalink

    @Mark

    Thanks. I’ve submitted the article to SilverlightCream, thanks for the suggestion.

  10. Mark Monster
    Posted January 20, 2010 at 7:59 pm | Permalink

    Hi Peter,

    Did you see that when you load the page, the cloud sits slightly to bottom left?

    -
    Mark Monster

  11. Peter
    Posted January 20, 2010 at 7:59 pm | Permalink

    @Mark,

    I have. Just now, it was sitting to the bottom right though :p
    I’m not sure where that is coming from. It looks a bit like a rendering issue in Silverlight. Maybe I’m missing some initialization code.

  12. Phil
    Posted January 20, 2010 at 7:59 pm | Permalink

    Hi Peter,
    I like this a lot. The only thing is I can’t seem to find the Axelerate3D library. Could you tell me where to get it?
    Thanks,
    Phil

  13. Peter
    Posted January 20, 2010 at 7:59 pm | Permalink

    Phil,

    You can find that here: http://www.codeplex.com/aXelerateSL3D/Wiki/View.aspx?title=Home

    In part 2 of this post another engine was suggested by Einar Ingebrigtsen, called Balder. Maybe that will work as well. http://balder.codeplex.com

  14. Michael
    Posted January 20, 2010 at 7:59 pm | Permalink

    Hey Peter,
    great stuff.
    I found out that the tags are slightly shifted to the right because the btnLink object does not have a ActualWidth,Height when you run the JavaScript init stuff.
    Question: what happens in the mouseenter, mouseleave events ? How do you stop the rotation ?
    Thanks a lot,
    Michael

  15. Ji
    Posted January 20, 2010 at 8:00 pm | Permalink

    Hi Peter!
    I am really interested in this 3D tag cloud.
    It’s gorgeous!.
    Could you send me this project code?? plz..

    and Can I convert this tag cloud to WPF application??
    I need this feature in Windows Application..
    It’s possible?

    Thans.^^

  16. Caleb
    Posted March 8, 2010 at 10:42 pm | Permalink

    I must be missing something, but in your code snippets you’re setting the LayoutRoot.MouseEnter and LayoutRoot.MouseLeave event handlers, but only provide the MouseMove event handler code.

    Just curious what should be happening in MouseEnter/MouseLeave.

  17. Peter Gerritsen
    Posted March 9, 2010 at 8:27 am | Permalink

    @Caleb

    You weren’t missing something. Apparently I never included the code in my post. I updated the post to show what is happening.

  18. Sebastian
    Posted July 15, 2010 at 12:04 pm | Permalink

    I’ve got a few problems with the implementation. I created a class Tag3D. And then all the functions defined outside the class. The elements LayoutRoot and RootCanvas are not found.

    Is there a way to get to the source code.
    Thanks a lot!

    Greets,
    Sebastian

One Trackback

  1. By Creating a 3D tagcloud in Silverlight (part 2) on March 4, 2010 at 10:06 pm

    [...] part 1 I showed you how to create the basics for a 3D tagcloud in Silverlight. In this part I’ll show [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>