ko.charts.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. // Licensed to Cloudera, Inc. under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. Cloudera, Inc. licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. ko.HUE_CHARTS = {
  17. TYPES: {
  18. LINECHART: "lines",
  19. BARCHART: "bars",
  20. POINTCHART: "points",
  21. PIECHART: "pie",
  22. MAP: "map"
  23. }
  24. };
  25. ko.bindingHandlers.pieChart = {
  26. update: function (element, valueAccessor) {
  27. var _options = valueAccessor();
  28. var _data = _options.transformer(_options.data);
  29. $(element).css("marginLeft", "auto");
  30. $(element).css("marginRight", "auto");
  31. if (typeof _options.maxWidth != "undefined") {
  32. var _max = _options.maxWidth * 1;
  33. $(element).width(Math.min($(element).parent().width(), _max));
  34. }
  35. if ($(element).find("svg").length > 0 && _data.length == 0) {
  36. $(element).find("svg").empty();
  37. }
  38. if (_data.length > 0 && isNaN(_data[0].value)) {
  39. _data = [];
  40. $(element).find("svg").empty();
  41. }
  42. if ($(element).is(":visible")) {
  43. nv.addGraph(function () {
  44. var _chart = nv.models.growingPieChart()
  45. .x(function (d) {
  46. return d.label
  47. })
  48. .y(function (d) {
  49. return d.value
  50. })
  51. .height($(element).width())
  52. .showLabels(true).showLegend(false);
  53. var _d3 = ($(element).find("svg").length > 0) ? d3.select($(element).find("svg")[0]) : d3.select($(element)[0]).append("svg");
  54. _d3.datum(_data)
  55. .transition().duration(150)
  56. .each("end", _options.onComplete != null ? _options.onComplete : void(0))
  57. .call(_chart);
  58. if (_options.fqs) {
  59. $.each(_options.fqs(), function (cnt, item) {
  60. if (item.field() == _options.field()) {
  61. _chart.selectSlices($.map(item.filter(), function(it) {return it.value();}));
  62. }
  63. });
  64. }
  65. nv.utils.windowResize(_chart.update);
  66. $(element).height($(element).width());
  67. $(element).parents(".card-widget").on("resize", function () {
  68. if (typeof _options.maxWidth != "undefined") {
  69. var _max = _options.maxWidth * 1;
  70. $(element).width(Math.min($(element).parent().width(), _max));
  71. }
  72. $(element).height($(element).width());
  73. _chart.update();
  74. });
  75. return _chart;
  76. }, function () {
  77. var _d3 = ($(element).find("svg").length > 0) ? d3.select($(element).find("svg")[0]) : d3.select($(element)[0]).append("svg");
  78. _d3.selectAll(".nv-slice").on("click",
  79. function (d, i) {
  80. if (typeof _options.onClick != "undefined") {
  81. chartsUpdatingState();
  82. _options.onClick(d);
  83. }
  84. });
  85. });
  86. }
  87. }
  88. };
  89. ko.bindingHandlers.barChart = {
  90. update: function (element, valueAccessor) {
  91. barChartBuilder(element, valueAccessor(), false);
  92. }
  93. };
  94. ko.bindingHandlers.timelineChart = {
  95. update: function (element, valueAccessor) {
  96. barChartBuilder(element, valueAccessor(), true);
  97. }
  98. };
  99. ko.bindingHandlers.lineChart = {
  100. update: function (element, valueAccessor) {
  101. lineChartBuilder(element, valueAccessor());
  102. }
  103. };
  104. ko.bindingHandlers.leafletMapChart = {
  105. update: function (element, valueAccessor) {
  106. var _options = valueAccessor();
  107. var _data = _options.transformer(valueAccessor().datum);
  108. function getMapBounds(lats, lngs) {
  109. lats = lats.sort();
  110. lngs = lngs.sort();
  111. return [
  112. [lats[lats.length - 1], lngs[lngs.length - 1]], // north-east
  113. [lats[0], lngs[0]] // south-west
  114. ]
  115. }
  116. if ($(element).data("map") != null) {
  117. try {
  118. $(element).data("map").remove();
  119. }
  120. catch (err) {
  121. if (typeof console != "undefined") {
  122. console.error(err);
  123. }
  124. }
  125. }
  126. var _lats = [];
  127. _data.forEach(function (item) {
  128. if (item.lat != null && $.isNumeric(item.lat)) {
  129. _lats.push(item.lat);
  130. }
  131. });
  132. var _lngs = [];
  133. _data.forEach(function (item) {
  134. if (item.lng != null && $.isNumeric(item.lng)) {
  135. _lngs.push(item.lng);
  136. }
  137. });
  138. if ($(element).parents(".tab-pane").length > 0){
  139. $(element).height($(element).parents(".tab-pane").height() - 100);
  140. }
  141. else {
  142. $(element).height(300);
  143. }
  144. if (((_options.visible != null && _options.visible) || _options.visible == null || typeof _options == "undefined") && _data.length > 0) {
  145. $(element).show();
  146. }
  147. else {
  148. $(element).hide();
  149. }
  150. var _map = null;
  151. if (element._map != null) {
  152. element._leaflet = false;
  153. element._map.remove();
  154. }
  155. if (_lats.length > 0 && _lngs.length > 0) {
  156. try {
  157. if (_map == null) {
  158. _map = L.map(element);
  159. L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
  160. attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
  161. }).addTo(_map);
  162. }
  163. _map.fitBounds(getMapBounds(_lats, _lngs));
  164. _data.forEach(function (item) {
  165. if (item.lng != null && item.lat != null) {
  166. var _addMarker = false;
  167. try {
  168. var _latLngObj = L.latLng(item.lat, item.lng);
  169. _addMarker = true;
  170. }
  171. catch (e){
  172. if (typeof console != "undefined") {
  173. console.error(e);
  174. }
  175. }
  176. if (_addMarker){
  177. var _marker = L.marker([item.lat, item.lng]).addTo(_map);
  178. if (item.label != null) {
  179. _marker.bindPopup(item.label);
  180. }
  181. }
  182. }
  183. });
  184. if (_options.onComplete != null) {
  185. _options.onComplete();
  186. }
  187. }
  188. catch (err) {
  189. $.jHueNotify.error(err.message);
  190. }
  191. }
  192. element._map = _map;
  193. }
  194. };
  195. ko.bindingHandlers.mapChart = {
  196. render: function (element, valueAccessor) {
  197. var _options = valueAccessor();
  198. $(element).empty();
  199. $(element).css("position", "relative");
  200. $(element).css("marginLeft", "auto");
  201. $(element).css("marginRight", "auto");
  202. if (typeof _options.maxWidth != "undefined") {
  203. var _max = _options.maxWidth * 1;
  204. $(element).width(Math.min($(element).parent().width(), _max));
  205. }
  206. $(element).height($(element).width() / 2.23);
  207. var _scope = typeof _options.data.scope != "undefined" ? String(_options.data.scope) : "world";
  208. var _data = _options.transformer(_options.data);
  209. var _maxWeight = 0;
  210. $(_data).each(function (cnt, item) {
  211. if (item.value > _maxWeight) _maxWeight = item.value;
  212. });
  213. var _chunk = _maxWeight / _data.length;
  214. var _mapdata = {};
  215. var _maphovers = {};
  216. var _fills = {};
  217. var _noncountries = [];
  218. if (_options.isScale) {
  219. _fills["defaultFill"] = HueColors.WHITE;
  220. var _colors = HueColors.scale(HueColors.LIGHT_BLUE, HueColors.DARK_BLUE, _data.length);
  221. $(_colors).each(function (cnt, item) {
  222. _fills["fill_" + cnt] = item;
  223. });
  224. $(_data).each(function (cnt, item) {
  225. var _place = item.label.toUpperCase();
  226. if (_place != null) {
  227. _mapdata[_place] = {
  228. fillKey: "fill_" + (Math.floor(item.value / _chunk) - 1),
  229. id: _place,
  230. cat: item.obj.cat,
  231. value: item.obj.value,
  232. selected: item.obj.selected
  233. };
  234. _maphovers[_place] = item.value;
  235. }
  236. else {
  237. _noncountries.push(item);
  238. }
  239. });
  240. }
  241. else {
  242. _fills["defaultFill"] = HueColors.LIGHT_BLUE;
  243. _fills["selected"] = HueColors.DARK_BLUE;
  244. $(_data).each(function (cnt, item) {
  245. var _place = item.label.toUpperCase();
  246. if (_place != null) {
  247. _mapdata[_place] = {
  248. fillKey: "selected",
  249. id: _place,
  250. cat: item.obj.cat,
  251. value: item.obj.value,
  252. selected: item.obj.selected
  253. };
  254. _maphovers[_place] = item.value;
  255. }
  256. else {
  257. _noncountries.push(item);
  258. }
  259. });
  260. }
  261. var _map = null;
  262. function createDatamap(element, options, fills, mapData, nonCountries, mapHovers) {
  263. _map = new Datamap({
  264. element: element,
  265. fills: fills,
  266. scope: _scope,
  267. data: mapData,
  268. onClick: function (data) {
  269. if (typeof options.onClick != "undefined") {
  270. chartsUpdatingState();
  271. options.onClick(data);
  272. }
  273. },
  274. done: function (datamap) {
  275. var _bubbles = [];
  276. if (options.enableGeocoding) {
  277. $(nonCountries).each(function (cnt, item) {
  278. HueGeo.getCityCoordinates(item.label, function (lat, lng) {
  279. _bubbles.push({
  280. fillKey: "selected",
  281. label: item.label,
  282. value: item.value,
  283. radius: 4,
  284. latitude: lat,
  285. longitude: lng
  286. });
  287. _map.bubbles(_bubbles, {
  288. popupTemplate: function (geo, data) {
  289. return '<div class="hoverinfo" style="text-align: center"><strong>' + data.label + '</strong><br/>' + item.value + '</div>'
  290. }
  291. });
  292. });
  293. });
  294. }
  295. },
  296. geographyConfig: {
  297. hideAntarctica: true,
  298. borderWidth: 1,
  299. borderColor: HueColors.DARK_BLUE,
  300. highlightOnHover: true,
  301. highlightFillColor: HueColors.DARK_BLUE,
  302. highlightBorderColor: HueColors.BLUE,
  303. selectedFillColor: HueColors.DARKER_BLUE,
  304. selectedBorderColor: HueColors.DARKER_BLUE,
  305. popupTemplate: function (geography, data) {
  306. var _hover = '';
  307. if (data != null) {
  308. _hover = '<br/>' + mapHovers[data.id];
  309. }
  310. return '<div class="hoverinfo" style="text-align: center"><strong>' + geography.properties.name + '</strong>' + _hover + '</div>'
  311. }
  312. }
  313. });
  314. if (options.onComplete != null) {
  315. options.onComplete();
  316. }
  317. }
  318. createDatamap(element, _options, _fills, _mapdata, _noncountries, _maphovers)
  319. $(element).parents(".card-widget").one("resize", function () {
  320. ko.bindingHandlers.mapChart.render(element, valueAccessor);
  321. });
  322. },
  323. init: function (element, valueAccessor) {
  324. ko.bindingHandlers.mapChart.render(element, valueAccessor)
  325. },
  326. update: function (element, valueAccessor) {
  327. ko.bindingHandlers.mapChart.render(element, valueAccessor);
  328. }
  329. };
  330. function lineChartBuilder(element, options) {
  331. var _datum = options.transformer(options.datum);
  332. $(element).height(300);
  333. if ($(element).find("svg").length > 0 && (_datum.length == 0 || _datum[0].values.length == 0)) {
  334. $(element).find("svg").empty();
  335. }
  336. if (_datum.length > 0 && _datum[0].values.length > 0 && (isNaN(_datum[0].values[0].x) || isNaN(_datum[0].values[0].y))) {
  337. _datum = [];
  338. $(element).find("svg").empty();
  339. }
  340. if ($(element).is(":visible")) {
  341. nv.addGraph(function () {
  342. var _chart = nv.models.lineWithBrushChart();
  343. if (_datum.length > 0 && _datum[0].values.length > 10) {
  344. _chart.enableSelection();
  345. }
  346. if (options.showControls != null) {
  347. _chart.showControls(false);
  348. }
  349. _chart.onSelectRange(function (from, to) {
  350. chartsUpdatingState();
  351. options.onSelectRange(from, to);
  352. });
  353. _chart.xAxis.showMaxMin(false);
  354. _chart.yAxis
  355. .tickFormat(d3.format(",0f"));
  356. var _d3 = ($(element).find("svg").length > 0) ? d3.select($(element).find("svg")[0]) : d3.select($(element)[0]).append("svg");
  357. _d3.datum(_datum)
  358. .transition().duration(150)
  359. .each("end", options.onComplete != null ? options.onComplete : void(0))
  360. .call(_chart);
  361. nv.utils.windowResize(_chart.update);
  362. return _chart;
  363. }, function () {
  364. var _d3 = ($(element).find("svg").length > 0) ? d3.select($(element).find("svg")[0]) : d3.select($(element)[0]).append("svg");
  365. _d3.selectAll(".nv-line").on("click",
  366. function (d, i) {
  367. if (typeof options.onClick != "undefined") {
  368. chartsUpdatingState();
  369. options.onClick(d);
  370. }
  371. });
  372. });
  373. }
  374. }
  375. function barChartBuilder(element, options, isTimeline) {
  376. var _datum = options.transformer(options.datum);
  377. $(element).height(300);
  378. var _isPivot = options.isPivot != null ? options.isPivot : false;
  379. if ($(element).find("svg").length > 0 && (_datum.length == 0 || _datum[0].values.length == 0)) {
  380. $(element).find("svg").empty();
  381. }
  382. if (_datum.length > 0 && _datum[0].values.length > 0 && isNaN(_datum[0].values[0].y)) {
  383. _datum = [];
  384. $(element).find("svg").empty();
  385. }
  386. if ($(element).is(":visible")) {
  387. nv.addGraph(function () {
  388. var _chart;
  389. var insertLinebreaks = function (d) {
  390. var _el = d3.select(this);
  391. var _mom = moment(d);
  392. if (_mom != null && _mom.isValid()) {
  393. var _words = _mom.format("hh:mm:ss YYYY-MM-DD").split(" ");
  394. _el.text("");
  395. for (var i = 0; i < _words.length; i++) {
  396. var tspan = _el.append("tspan").text(_words[i]);
  397. if (i > 0) {
  398. tspan.attr("x", 0).attr("dy", "15");
  399. }
  400. }
  401. }
  402. };
  403. if (isTimeline) {
  404. if ($(element).find("svg").length > 0 && $(element).find(".nv-discreteBarWithAxes").length > 0) {
  405. $(element).find("svg").empty();
  406. }
  407. _chart = nv.models.multiBarWithBrushChart();
  408. if (_datum.length > 0 && _datum[0].values.length > 10) {
  409. _chart.enableSelection();
  410. }
  411. _chart.onSelectRange(function (from, to) {
  412. chartsUpdatingState();
  413. options.onSelectRange(from, to);
  414. });
  415. _chart.xAxis.tickFormat(d3.time.format("%Y-%m-%d %H:%M:%S"));
  416. _chart.multibar.hideable(true);
  417. _chart.multibar.stacked(typeof options.stacked != "undefined" ? options.stacked : false);
  418. _chart.onStateChange(options.onStateChange);
  419. _chart.onChartUpdate(function () {
  420. _d3.selectAll("g.nv-x.nv-axis g text").each(insertLinebreaks);
  421. });
  422. }
  423. else {
  424. var _isDiscrete = false;
  425. for (var j = 0; j < _datum.length; j++) {
  426. for (var i = 0; i < _datum[j].values.length; i++) {
  427. if (isNaN(_datum[j].values[i].x * 1)) {
  428. _isDiscrete = true;
  429. break;
  430. }
  431. }
  432. }
  433. if (_isDiscrete && ! _isPivot) {
  434. if ($(element).find("svg").length > 0 && $(element).find(".nv-multiBarWithLegend").length > 0) {
  435. $(element).find("svg").empty();
  436. }
  437. _chart = nv.models.growingDiscreteBarChart()
  438. .x(function (d) {
  439. return d.x
  440. })
  441. .y(function (d) {
  442. return d.y
  443. })
  444. .staggerLabels(true);
  445. }
  446. else {
  447. if ($(element).find("svg").length > 0 && $(element).find(".nv-discreteBarWithAxes").length > 0) {
  448. $(element).find("svg").empty();
  449. }
  450. _chart = nv.models.multiBarWithBrushChart();
  451. if (_datum.length > 0 && _datum[0].values.length > 10) {
  452. _chart.enableSelection();
  453. }
  454. if (_isPivot){
  455. _chart.hideSelection();
  456. }
  457. else {
  458. _chart.xAxis.showMaxMin(false).tickFormat(d3.format(",0f"));
  459. }
  460. _chart.multibar.hideable(true);
  461. _chart.multibar.stacked(typeof options.stacked != "undefined" ? options.stacked : false);
  462. _chart.onStateChange(options.onStateChange);
  463. _chart.onSelectRange(function (from, to) {
  464. chartsUpdatingState();
  465. options.onSelectRange(from, to);
  466. });
  467. }
  468. }
  469. _chart.transitionDuration(0);
  470. _chart.yAxis
  471. .tickFormat(d3.format(",0f"));
  472. var _d3 = ($(element).find("svg").length > 0) ? d3.select($(element).find("svg")[0]) : d3.select($(element)[0]).append("svg");
  473. _d3.datum(_datum)
  474. .transition().duration(150)
  475. .each("end", function () {
  476. if (options.onComplete != null) {
  477. options.onComplete();
  478. }
  479. if (isTimeline) {
  480. _d3.selectAll("g.nv-x.nv-axis g text").each(insertLinebreaks);
  481. }
  482. }).call(_chart);
  483. $.each(options.fqs(), function (cnt, item) {
  484. if (item.field() == options.field) {
  485. _chart.selectBars($.map(item.filter(), function(it) {return it.value();}));
  486. }
  487. if (item.field().indexOf(":") > -1){
  488. _chart.selectBars({
  489. field: item.field(),
  490. selected: $.map(item.filter(), function(it) {return it.value();})
  491. });
  492. }
  493. });
  494. nv.utils.windowResize(_chart.update);
  495. return _chart;
  496. }, function () {
  497. var _d3 = ($(element).find("svg").length > 0) ? d3.select($(element).find("svg")[0]) : d3.select($(element)[0]).append("svg");
  498. _d3.selectAll(".nv-bar").on("click",
  499. function (d, i) {
  500. if (typeof options.onClick != "undefined") {
  501. chartsUpdatingState();
  502. options.onClick(d);
  503. }
  504. });
  505. });
  506. }
  507. }
  508. ko.bindingHandlers.partitionChart = {
  509. update: function (element, valueAccessor) {
  510. var MIN_HEIGHT_FOR_TOOLTIP = 24;
  511. var _options = valueAccessor();
  512. var _data = _options.transformer(valueAccessor().datum);
  513. var _w = $(element).width(),
  514. _h = 300,
  515. _x = d3.scale.linear().range([0, _w]),
  516. _y = d3.scale.linear().range([0, _h]);
  517. var _tip = d3.tip()
  518. .attr("class", "d3-tip")
  519. .html(function (d) {
  520. if (d.depth < 2) {
  521. return d.name + " (" + d.value + ")";
  522. }
  523. else {
  524. return d.parent.name + " - " + d.name + " (" + d.value + ")";
  525. }
  526. })
  527. .offset([-12, 0])
  528. var _svg = d3.select(element).append("svg:svg");
  529. _svg.call(_tip);
  530. var _vis = ($(element).find("svg").length > 0) ? d3.select($(element).find("svg")[0]) : d3.select($(element)[0]).append("svg");
  531. _vis.attr("class", "partitionChart")
  532. .style("width", _w + "px")
  533. .style("height", _h + "px")
  534. .append("svg:svg")
  535. .attr("width", _w)
  536. .attr("height", _h);
  537. var _partition = d3.layout.partition()
  538. .value(function (d) {
  539. return d.size;
  540. });
  541. var g = _vis.selectAll("g")
  542. .data(_partition.nodes(_data))
  543. .enter().append("svg:g")
  544. .attr("transform", function (d) {
  545. return "translate(" + _x(d.y) + "," + _y(d.x) + ")";
  546. })
  547. .on("mouseover", function (d, i) {
  548. if (element.querySelectorAll("rect")[i].getBBox().height < MIN_HEIGHT_FOR_TOOLTIP) {
  549. _tip.attr("class", "d3-tip").show(d);
  550. }
  551. if (this.__data__.parent == undefined) return;
  552. d3.select(this).select("rect").classed("mouseover", true)
  553. })
  554. .on("mouseout", function (d, i) {
  555. if (element.querySelectorAll("rect")[i].getBBox().height < MIN_HEIGHT_FOR_TOOLTIP) {
  556. _tip.attr("class", "d3-tip").show(d);
  557. _tip.hide();
  558. }
  559. d3.select(this).select("rect").classed("mouseover", false)
  560. });
  561. if (typeof _options.zoomable == "undefined" || _options.zoomable) {
  562. g.on("click", click)
  563. .on("dblclick", function (d, i) {
  564. if (typeof _options.onClick != "undefined") {
  565. chartsUpdatingState();
  566. _options.onClick(d);
  567. }
  568. });
  569. }
  570. else {
  571. g.on("click", function (d, i) {
  572. if (typeof _options.onClick != "undefined") {
  573. chartsUpdatingState();
  574. _options.onClick(d);
  575. }
  576. });
  577. }
  578. var _kx = _w / _data.dx,
  579. _ky = _h / 1;
  580. var _colors = HueColors.scale(HueColors.DARK_BLUE, HueColors.BLUE, 5)
  581. g.append("svg:rect")
  582. .attr("width", _data.dy * _kx)
  583. .attr("height", function (d) {
  584. return d.dx * _ky;
  585. })
  586. .attr("class", function (d) {
  587. return d.children ? "parent" : "child";
  588. })
  589. .attr("stroke", HueColors.DARK_BLUE)
  590. .attr("fill", function (d, i) {
  591. return _colors[d.depth];
  592. });
  593. g.append("svg:text")
  594. .attr("transform", transform)
  595. .attr("dy", ".35em")
  596. .style("opacity", function (d) {
  597. return d.dx * _ky > 12 ? 1 : 0;
  598. })
  599. .text(function (d) {
  600. if (d.depth < 2) {
  601. return d.name + " (" + d.value + ")";
  602. }
  603. else {
  604. return d.parent.name + " - " + d.name + " (" + d.value + ")";
  605. }
  606. });
  607. d3.select(window)
  608. .on("click", function () {
  609. click(_data);
  610. });
  611. function click(d) {
  612. _tip.hide();
  613. if (!d.children) return;
  614. _kx = (d.y ? _w - 40 : _w) / (1 - d.y);
  615. _ky = _h / d.dx;
  616. _x.domain([d.y, 1]).range([d.y ? 40 : 0, _w]);
  617. _y.domain([d.x, d.x + d.dx]);
  618. var t = g.transition()
  619. .duration(d3.event.altKey ? 7500 : 750)
  620. .attr("transform", function (d) {
  621. return "translate(" + _x(d.y) + "," + _y(d.x) + ")";
  622. });
  623. t.select("rect")
  624. .attr("width", d.dy * _kx)
  625. .attr("height", function (d) {
  626. return d.dx * _ky;
  627. });
  628. t.select("text")
  629. .attr("transform", transform)
  630. .style("opacity", function (d) {
  631. return d.dx * _ky > 12 ? 1 : 0;
  632. });
  633. d3.event.stopPropagation();
  634. }
  635. function transform(d) {
  636. return "translate(8," + d.dx * _ky / 2 + ")";
  637. }
  638. }
  639. };
  640. function chartsUpdatingState() {
  641. $(document).find("svg").css("opacity", "0.5");
  642. }
  643. var tipBuilder = function () {
  644. var direction = d3_tip_direction,
  645. offset = d3_tip_offset,
  646. html = d3_tip_html,
  647. node = initNode(),
  648. svg = null,
  649. point = null,
  650. target = null
  651. function tip(vis) {
  652. svg = getSVGNode(vis)
  653. point = svg.createSVGPoint()
  654. document.body.appendChild(node)
  655. }
  656. // Public - show the tooltip on the screen
  657. //
  658. // Returns a tip
  659. tip.show = function () {
  660. var args = Array.prototype.slice.call(arguments)
  661. if (args[args.length - 1] instanceof SVGElement) target = args.pop()
  662. var content = html.apply(this, args),
  663. poffset = offset.apply(this, args),
  664. dir = direction.apply(this, args),
  665. nodel = d3.select(node), i = 0,
  666. coords
  667. nodel.html(content)
  668. .style({ opacity: 1, "pointer-events": "all" })
  669. while (i--) nodel.classed(directions[i], false)
  670. coords = direction_callbacks.get(dir).apply(this)
  671. nodel.classed(dir, true).style({
  672. top: (coords.top + poffset[0]) + "px",
  673. left: (coords.left + poffset[1]) + "px"
  674. })
  675. return tip
  676. }
  677. // Public - hide the tooltip
  678. //
  679. // Returns a tip
  680. tip.hide = function () {
  681. nodel = d3.select(node)
  682. nodel.style({ opacity: 0, "pointer-events": "none" })
  683. return tip
  684. }
  685. // Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value.
  686. //
  687. // n - name of the attribute
  688. // v - value of the attribute
  689. //
  690. // Returns tip or attribute value
  691. tip.attr = function (n, v) {
  692. if (arguments.length < 2 && typeof n === "string") {
  693. return d3.select(node).attr(n)
  694. } else {
  695. var args = Array.prototype.slice.call(arguments)
  696. d3.selection.prototype.attr.apply(d3.select(node), args)
  697. }
  698. return tip
  699. }
  700. // Public: Proxy style calls to the d3 tip container. Sets or gets a style value.
  701. //
  702. // n - name of the property
  703. // v - value of the property
  704. //
  705. // Returns tip or style property value
  706. tip.style = function (n, v) {
  707. if (arguments.length < 2 && typeof n === "string") {
  708. return d3.select(node).style(n)
  709. } else {
  710. var args = Array.prototype.slice.call(arguments)
  711. d3.selection.prototype.style.apply(d3.select(node), args)
  712. }
  713. return tip
  714. }
  715. // Public: Set or get the direction of the tooltip
  716. //
  717. // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
  718. // sw(southwest), ne(northeast) or se(southeast)
  719. //
  720. // Returns tip or direction
  721. tip.direction = function (v) {
  722. if (!arguments.length) return direction
  723. direction = v == null ? v : d3.functor(v)
  724. return tip
  725. }
  726. // Public: Sets or gets the offset of the tip
  727. //
  728. // v - Array of [x, y] offset
  729. //
  730. // Returns offset or
  731. tip.offset = function (v) {
  732. if (!arguments.length) return offset
  733. offset = v == null ? v : d3.functor(v)
  734. return tip
  735. }
  736. // Public: sets or gets the html value of the tooltip
  737. //
  738. // v - String value of the tip
  739. //
  740. // Returns html value or tip
  741. tip.html = function (v) {
  742. if (!arguments.length) return html
  743. html = v == null ? v : d3.functor(v)
  744. return tip
  745. }
  746. function d3_tip_direction() {
  747. return "n"
  748. }
  749. function d3_tip_offset() {
  750. return [0, 0]
  751. }
  752. function d3_tip_html() {
  753. return " "
  754. }
  755. var direction_callbacks = d3.map({
  756. n: direction_n,
  757. s: direction_s,
  758. e: direction_e,
  759. w: direction_w,
  760. nw: direction_nw,
  761. ne: direction_ne,
  762. sw: direction_sw,
  763. se: direction_se
  764. }),
  765. directions = direction_callbacks.keys()
  766. function direction_n() {
  767. var bbox = getScreenBBox()
  768. return {
  769. top: bbox.n.y - node.offsetHeight,
  770. left: bbox.n.x - node.offsetWidth / 2
  771. }
  772. }
  773. function direction_s() {
  774. var bbox = getScreenBBox()
  775. return {
  776. top: bbox.s.y,
  777. left: bbox.s.x - node.offsetWidth / 2
  778. }
  779. }
  780. function direction_e() {
  781. var bbox = getScreenBBox()
  782. return {
  783. top: bbox.e.y - node.offsetHeight / 2,
  784. left: bbox.e.x
  785. }
  786. }
  787. function direction_w() {
  788. var bbox = getScreenBBox()
  789. return {
  790. top: bbox.w.y - node.offsetHeight / 2,
  791. left: bbox.w.x - node.offsetWidth
  792. }
  793. }
  794. function direction_nw() {
  795. var bbox = getScreenBBox()
  796. return {
  797. top: bbox.nw.y - node.offsetHeight,
  798. left: bbox.nw.x - node.offsetWidth
  799. }
  800. }
  801. function direction_ne() {
  802. var bbox = getScreenBBox()
  803. return {
  804. top: bbox.ne.y - node.offsetHeight,
  805. left: bbox.ne.x
  806. }
  807. }
  808. function direction_sw() {
  809. var bbox = getScreenBBox()
  810. return {
  811. top: bbox.sw.y,
  812. left: bbox.sw.x - node.offsetWidth
  813. }
  814. }
  815. function direction_se() {
  816. var bbox = getScreenBBox()
  817. return {
  818. top: bbox.se.y,
  819. left: bbox.e.x
  820. }
  821. }
  822. function initNode() {
  823. var node = d3.select(document.createElement("div"))
  824. node.style({
  825. position: "absolute",
  826. opacity: 0,
  827. pointerEvents: "none",
  828. boxSizing: "border-box"
  829. })
  830. return node.node()
  831. }
  832. function getSVGNode(el) {
  833. el = el.node()
  834. if (el != null) {
  835. if (el.tagName != null && el.tagName.toLowerCase() == "svg")
  836. return el
  837. return el.ownerSVGElement
  838. }
  839. }
  840. // Private - gets the screen coordinates of a shape
  841. //
  842. // Given a shape on the screen, will return an SVGPoint for the directions
  843. // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest),
  844. // sw(southwest).
  845. //
  846. // +-+-+
  847. // | |
  848. // + +
  849. // | |
  850. // +-+-+
  851. //
  852. // Returns an Object {n, s, e, w, nw, sw, ne, se}
  853. function getScreenBBox() {
  854. var targetel = target || d3.event.target,
  855. bbox = {},
  856. matrix = targetel.getScreenCTM(),
  857. tbbox = targetel.getBBox(),
  858. width = tbbox.width,
  859. height = tbbox.height,
  860. x = tbbox.x,
  861. y = tbbox.y,
  862. scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
  863. scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
  864. point.x = x + scrollLeft
  865. point.y = y + scrollTop
  866. bbox.nw = point.matrixTransform(matrix)
  867. point.x += width
  868. bbox.ne = point.matrixTransform(matrix)
  869. point.y += height
  870. bbox.se = point.matrixTransform(matrix)
  871. point.x -= width
  872. bbox.sw = point.matrixTransform(matrix)
  873. point.y -= height / 2
  874. bbox.w = point.matrixTransform(matrix)
  875. point.x += width
  876. bbox.e = point.matrixTransform(matrix)
  877. point.x -= width / 2
  878. point.y -= height / 2
  879. bbox.n = point.matrixTransform(matrix)
  880. point.y += height
  881. bbox.s = point.matrixTransform(matrix)
  882. return bbox
  883. }
  884. return tip
  885. };
  886. d3.tip = tipBuilder;