Creating a Silverlight TagCloud UserControl
TweetI was just looking around the web to find a Silverlight Control that's has the features of a TagCloud. There are a lot of TagClouds in the world of Web 2.0, so probably almost everyone knows the way they work.
The looks of a Cloud Item
Just very simple a class that has a name, used for displaying and a weight, used for determining the font-size. You'll probably want it to have more properties but that can be added when needed I guess.
1 public class CloudItem 2 { 3 public string Name { get; set; } 4 public int Weight { get; set; } 5 }
Determining the style for a CloudItem based on the Weight
I've been Googling around to find a nice approach for determining the font-size for a Cloud-Item. I finally found a blog-post about Tag Clouds in ColdFusion that explains an approach that I also followed in this UserControl. It basically supports 5 different font-sizes: Smallest, Small, Medium, Large and Largest. The definition for which font-size is determined by using the minimum and maximum Weights. I did this in a very simple Linq query.
int minWeight = cloudItems.Min((cloudItem => cloudItem.Weight)); int maxWeight = cloudItems.Max((cloudItem => cloudItem.Weight));
And the following method determines which of the five styles should be used.
1 public string DetermineResourceForWeight(CloudItem cloudItem, int minWeight, int maxWeight) 2 { 3 int distribution = (maxWeight - minWeight)/3; 4 if (cloudItem.Weight == minWeight) 5 return "CloudTagStyleSmallest"; 6 if (cloudItem.Weight == maxWeight) 7 return "CloudTagStyleLargest"; 8 if (cloudItem.Weight > (minWeight + (distribution*2))) 9 return "CloudTagStyleLarge"; 10 if (cloudItem.Weight > (minWeight + (distribution))) 11 return "CloudTagStyleMedium"; 12 return "CloudTagStyleSmall"; 13 }
I added the styles to the App.xaml
1 <Application.Resources> 2 <Style x:Key="CloudTagStyleLargest" TargetType="TextBlock"> 3 <Setter Property="Margin" Value="3,3,3,3"/> 4 <Setter Property="FontSize" Value="30"/> 5 </Style> 6 <Style x:Key="CloudTagStyleLarge" TargetType="TextBlock"> 7 <Setter Property="Margin" Value="3,3,3,3"/> 8 <Setter Property="FontSize" Value="25"/> 9 </Style> 10 <Style x:Key="CloudTagStyleMedium" TargetType="TextBlock"> 11 <Setter Property="Margin" Value="3,3,3,3"/> 12 <Setter Property="FontSize" Value="20"/> 13 </Style> 14 <Style x:Key="CloudTagStyleSmall" TargetType="TextBlock"> 15 <Setter Property="Margin" Value="3,3,3,3"/> 16 <Setter Property="FontSize" Value="15"/> 17 </Style> 18 <Style x:Key="CloudTagStyleSmallest" TargetType="TextBlock"> 19 <Setter Property="Margin" Value="3,3,3,3"/> 20 <Setter Property="FontSize" Value="10"/> 21 </Style> 22 <Style x:Key="CloudTagStylePanel" TargetType="TagCloud:WrapPanel"> 23 <Setter Property="Background" Value="White"/> 24 </Style> 25 </Application.Resources>
Converting a CloudItem to a TextBlock
Now we have ourselves some code that covers the logic we have to convert every CloudItem into a TextBlock. The following function converts one CloudItem.
1 public TextBlock ConvertToTextBlock(CloudItem cloudItem, int minWeight, int maxWeight) 2 { 3 return new TextBlock 4 { 5 Text = cloudItem.Name, 6 Style = 7 Application.Current.Resources 8 [DetermineResourceForWeight(cloudItem, minWeight, maxWeight)] as Style, 9 Tag = cloudItem 10 }; 11 }
And this does the conversion for a collection of CloudItems.
IEnumerable<TextBlock> textBlocks = (from cloudItem in cloudItems select cloudItemService.ConvertToTextBlock(cloudItem, minWeight, maxWeight));
Dynamically adding TextBlocks to a Panel
We can add those items very easy to a Panel. We just have to remove all the items that are in it (you'll never know if there are any) and then just add the TextBlocks.
1 LayoutRoot.Children.Clear(); 2 3 foreach (TextBlock textBlock in textBlocks) 4 { 5 LayoutRoot.Children.Add(textBlock); 6 }
Wrapping the TextBlocks the right way
But yes, there needs to be some layout done. I was not sure if I needed to put time in doing a thing like layout on Panel like Wrapping or some kind. I did again some Googling and found a very nice solution that's works in this situation, so far. So I just used it!
Interaction with the TagCloud UserControl
It's important to know how you can interact with the TagCloud UserControl. You can change the collection of CloudItems in the following way.
1 var tagCloud = new TagCloud.TagCloud(); 2 tagCloud.CloudItems = new List<CloudItem> 3 { 4 new CloudItem {Name = ".NET", Weight = 6}, 5 new CloudItem {Name = "API", Weight = 2}, 6 new CloudItem {Name = "C#", Weight = 3}, 7 new CloudItem {Name = "Calendar", Weight = 1}, 8 new CloudItem {Name = "Mail", Weight = 2}, 9 new CloudItem {Name = "PIM", Weight = 4}, 10 new CloudItem {Name = "Reader", Weight = 1}, 11 new CloudItem {Name = "Silverlight", Weight = 5}, 12 new CloudItem {Name = "Task", Weight = 1} 13 };
And you can also listen to clicks on CloudItems by using this code. The CloudItemEventArgs contains the original CloudItem property. Even when you inherit from CloudItem to add properties these will be passed back through the CloudItemEventArgs.
1 tagCloud.CloudItemClicked += tagCloud_CloudItemClicked; 2 3 void tagCloud_CloudItemClicked(object sender, CloudItemEventArgs e) 4 { 5 throw new NotImplementedException(); 6 }
I think this control is quite done now. It can become more feature-rich but does the things I want it to do. You can find the sources here.
Please leave a message if you're using this control. It's free, but I'd like to know if the control get's used a lot.




Denislav Savkov with a selection helper class, Mark Monster with SL Tag Cloud, Mike Taulty on asmx web
very nice and so sample !! thanks !!
sorry but I have a problem with tagCloud_CloudItemClicked.
The event is never called when I click in the left mouse ???
any idea ????
I am using vs2008+XP SL2 beta2
Did you add the code for listening to the event?
tagCloud.CloudItemClicked += tagCloud_CloudItemClicked;
The tag cloud item I should display must be not greater than the height of the grid contrainer of this TagCloud.
Where should I apply this?
What you can do is limit the list of cloudItems. This can be done within the following part
IEnumerable<TextBlock> textBlocks =
(from cloudItem in cloudItems
select cloudItemService.ConvertToTextBlock(cloudItem, minWeight, maxWeight)).Take(10)
If you want to do things like paging, you can easily combine the Take and Skip methods.
To explain further about my scenario in paging, the items I should display is based on the grid container of my tagcloud.
ex: I have 10 tag items, because I have a predefine height of tag cloud, the visible tag item is 5. So then the others should be in the next page.
Sorry for my lengthy question. I just barely need this. Thanks!
It is possible to measure the highest item per row. If the the max visible height is reached you can stop adding items. Maybe it's good to give each item an ascending number so you are able to easily select the next items that that should be visible on the next page.
But in the end it all means measuring. I know this can be very difficult. I've had some trouble myself in creating a flow-panel that does the layout I want it to do. I hope this helps you in the right direction.
Thanks again!
I can understand the difficulty of implementing it in the Wrap Panel. I do not think about the Wrap Panel as an easy control. It's already half a year since I implemented it. I hope you will be able to find a solution that fits for your situation.
1. cloudItems does not exist in current context
Also, do I not need anything in page.xaml?
i downloaded the sources from you. but when i opened up the solution, there seemed to be a warning.
Unable to update auto-refresh reference 'system.web.silverlight.dll'. Cannot find assembly 'C:\Documents and Settings\Administrator\Desktop\Program Files\Microsoft SDKs\Silverlight\v2.0\Libraries\Server\System.Web.Silverlight.dll'.
can i know where i can get this .dll file from??
it would be great if you can help me. because i am not good with IT-related stuffs.
This dll is no longer needed. It didn't much anyway, but basically it included some html that can show the silverlight application.
Please read this article to understand how to include your Silverlight application using html: http://msdn.microsoft.com/en-us/library/cc189089%28VS.95%29.aspx
i cant deploy it to my sharepoint site. any idea?
i realised the reason why i cant deploy to the sharepoint site is because the source you gave is not a webpart project. it is a web application. am i right?
however, when i transferred all your codes to the new webpart project that i created, i encountered many errors.
why is this so?
i'm in need of help. i managed to build the application successfully. however, upon debugging, the tags are static. as shown above. it did not move like the one you have. can help me with that? do i have to add in any additional codes for it to work and move?? pls pls i need help.
The rotating 3-D Tagcloud is based on the articles of Peter Gerritsen.
http://blogs.tamtam.nl/peterg/2009/02/13/CreatingA3DTagcloudInSilverlightPart1.aspx
http://blogs.tamtam.nl/peterg/2009/02/19/CreatingA3DTagcloudInSilverlightPart2.aspx
so i just have to add in the codes from
http://blogs.tamtam.nl/peterg/2009/02/13/CreatingA3DTagcloudInSilverlightPart1.aspx?
thanks - really helpful. I've used this as a base for implementing my own control.
Question for you: how would you recommend implementing this so that there was no space between the items? In other words, in the current version the height of each row is the height of the tallest item in the row. How would you implement it so that the spaces between all words were filled?
cheers
will.