jqcloud-1.0.4.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /*!
  2. * jQCloud Plugin for jQuery
  3. *
  4. * Version 1.0.4
  5. *
  6. * Copyright 2011, Luca Ongaro
  7. * Licensed under the MIT license.
  8. *
  9. * Date: 2013-05-09 18:54:22 +0200
  10. */
  11. (function( $ ) {
  12. "use strict";
  13. $.fn.jQCloud = function(word_array, options) {
  14. // Reference to the container element
  15. var $this = this;
  16. // Namespace word ids to avoid collisions between multiple clouds
  17. var cloud_namespace = $this.attr('id') || Math.floor((Math.random()*1000000)).toString(36);
  18. // Default options value
  19. var default_options = {
  20. width: $this.width(),
  21. height: $this.height(),
  22. center: {
  23. x: ((options && options.width) ? options.width : $this.width()) / 2.0,
  24. y: ((options && options.height) ? options.height : $this.height()) / 2.0
  25. },
  26. delayedMode: word_array.length > 50,
  27. shape: false, // It defaults to elliptic shape
  28. encodeURI: true,
  29. removeOverflowing: true
  30. };
  31. options = $.extend(default_options, options || {});
  32. // Add the "jqcloud" class to the container for easy CSS styling, set container width/height
  33. $this.addClass("jqcloud").width(options.width).height(options.height);
  34. // Container's CSS position cannot be 'static'
  35. if ($this.css("position") === "static") {
  36. $this.css("position", "relative");
  37. }
  38. var drawWordCloud = function() {
  39. // Helper function to test if an element overlaps others
  40. var hitTest = function(elem, other_elems) {
  41. // Pairwise overlap detection
  42. var overlapping = function(a, b) {
  43. if (Math.abs(2.0*a.offsetLeft + a.offsetWidth - 2.0*b.offsetLeft - b.offsetWidth) < a.offsetWidth + b.offsetWidth) {
  44. if (Math.abs(2.0*a.offsetTop + a.offsetHeight - 2.0*b.offsetTop - b.offsetHeight) < a.offsetHeight + b.offsetHeight) {
  45. return true;
  46. }
  47. }
  48. return false;
  49. };
  50. var i = 0;
  51. // Check elements for overlap one by one, stop and return false as soon as an overlap is found
  52. for(i = 0; i < other_elems.length; i++) {
  53. if (overlapping(elem, other_elems[i])) {
  54. return true;
  55. }
  56. }
  57. return false;
  58. };
  59. // Make sure every weight is a number before sorting
  60. for (var i = 0; i < word_array.length; i++) {
  61. word_array[i].weight = parseFloat(word_array[i].weight, 10);
  62. }
  63. // Sort word_array from the word with the highest weight to the one with the lowest
  64. word_array.sort(function(a, b) { if (a.weight < b.weight) {return 1;} else if (a.weight > b.weight) {return -1;} else {return 0;} });
  65. var step = (options.shape === "rectangular") ? 18.0 : 2.0,
  66. already_placed_words = [],
  67. aspect_ratio = options.width / options.height;
  68. // Function to draw a word, by moving it in spiral until it finds a suitable empty place. This will be iterated on each word.
  69. var drawOneWord = function(index, word) {
  70. // Define the ID attribute of the span that will wrap the word, and the associated jQuery selector string
  71. var word_id = cloud_namespace + "_word_" + index,
  72. word_selector = "#" + word_id,
  73. angle = 6.28 * Math.random(),
  74. radius = 0.0,
  75. // Only used if option.shape == 'rectangular'
  76. steps_in_direction = 0.0,
  77. quarter_turns = 0.0,
  78. weight = 5,
  79. custom_class = "",
  80. inner_html = "",
  81. word_span;
  82. // Extend word html options with defaults
  83. word.html = $.extend(word.html, {id: word_id});
  84. // If custom class was specified, put them into a variable and remove it from html attrs, to avoid overwriting classes set by jQCloud
  85. if (word.html && word.html["class"]) {
  86. custom_class = word.html["class"];
  87. delete word.html["class"];
  88. }
  89. // Check if min(weight) > max(weight) otherwise use default
  90. if (word_array[0].weight > word_array[word_array.length - 1].weight) {
  91. // Linearly map the original weight to a discrete scale from 1 to 10
  92. weight = Math.round((word.weight - word_array[word_array.length - 1].weight) /
  93. (word_array[0].weight - word_array[word_array.length - 1].weight) * 9.0) + 1;
  94. }
  95. word_span = $('<span>').attr(word.html).addClass('w' + weight + " " + custom_class);
  96. // Append link if word.url attribute was set
  97. if (word.link) {
  98. // If link is a string, then use it as the link href
  99. if (typeof word.link === "string") {
  100. word.link = {href: word.link};
  101. }
  102. // Extend link html options with defaults
  103. if ( options.encodeURI ) {
  104. word.link = $.extend(word.link, { href: encodeURI(word.link.href).replace(/'/g, "%27") });
  105. }
  106. inner_html = $('<a>').attr(word.link).text(word.text);
  107. } else {
  108. inner_html = word.text;
  109. }
  110. word_span.append(inner_html);
  111. // Bind handlers to words
  112. if (!!word.handlers) {
  113. for (var prop in word.handlers) {
  114. if (word.handlers.hasOwnProperty(prop) && typeof word.handlers[prop] === 'function') {
  115. $(word_span).bind(prop, word.handlers[prop]);
  116. }
  117. }
  118. }
  119. $this.append(word_span);
  120. var width = word_span.width(),
  121. height = word_span.height(),
  122. left = options.center.x - width / 2.0,
  123. top = options.center.y - height / 2.0;
  124. // Save a reference to the style property, for better performance
  125. var word_style = word_span[0].style;
  126. word_style.position = "absolute";
  127. word_style.left = left + "px";
  128. word_style.top = top + "px";
  129. while(hitTest(word_span[0], already_placed_words)) {
  130. // option shape is 'rectangular' so move the word in a rectangular spiral
  131. if (options.shape === "rectangular") {
  132. steps_in_direction++;
  133. if (steps_in_direction * step > (1 + Math.floor(quarter_turns / 2.0)) * step * ((quarter_turns % 4 % 2) === 0 ? 1 : aspect_ratio)) {
  134. steps_in_direction = 0.0;
  135. quarter_turns++;
  136. }
  137. switch(quarter_turns % 4) {
  138. case 1:
  139. left += step * aspect_ratio + Math.random() * 2.0;
  140. break;
  141. case 2:
  142. top -= step + Math.random() * 2.0;
  143. break;
  144. case 3:
  145. left -= step * aspect_ratio + Math.random() * 2.0;
  146. break;
  147. case 0:
  148. top += step + Math.random() * 2.0;
  149. break;
  150. }
  151. } else { // Default settings: elliptic spiral shape
  152. radius += step;
  153. angle += (index % 2 === 0 ? 1 : -1)*step;
  154. left = options.center.x - (width / 2.0) + (radius*Math.cos(angle)) * aspect_ratio;
  155. top = options.center.y + radius*Math.sin(angle) - (height / 2.0);
  156. }
  157. word_style.left = left + "px";
  158. word_style.top = top + "px";
  159. }
  160. // Don't render word if part of it would be outside the container
  161. if (options.removeOverflowing && (left < 0 || top < 0 || (left + width) > options.width || (top + height) > options.height)) {
  162. word_span.remove()
  163. return;
  164. }
  165. already_placed_words.push(word_span[0]);
  166. // Invoke callback if existing
  167. if ($.isFunction(word.afterWordRender)) {
  168. word.afterWordRender.call(word_span);
  169. }
  170. };
  171. var drawOneWordDelayed = function(index) {
  172. index = index || 0;
  173. if (!$this.is(':visible')) { // if not visible then do not attempt to draw
  174. setTimeout(function(){drawOneWordDelayed(index);},10);
  175. return;
  176. }
  177. if (index < word_array.length) {
  178. drawOneWord(index, word_array[index]);
  179. setTimeout(function(){drawOneWordDelayed(index + 1);}, 10);
  180. } else {
  181. if ($.isFunction(options.afterCloudRender)) {
  182. options.afterCloudRender.call($this);
  183. }
  184. }
  185. };
  186. // Iterate drawOneWord on every word. The way the iteration is done depends on the drawing mode (delayedMode is true or false)
  187. if (options.delayedMode){
  188. drawOneWordDelayed();
  189. }
  190. else {
  191. $.each(word_array, drawOneWord);
  192. if ($.isFunction(options.afterCloudRender)) {
  193. options.afterCloudRender.call($this);
  194. }
  195. }
  196. };
  197. // Delay execution so that the browser can render the page before the computatively intensive word cloud drawing
  198. setTimeout(function(){drawWordCloud();}, 10);
  199. return $this;
  200. };
  201. })(jQuery);