chart/scatterPlot.js

  1. import * as d3 from "d3";
  2. import dataTransform from "../dataTransform.js";
  3. import component from "../component.js";
  4. import { dispatch } from "../events.js";
  5. import { createScene } from "../base.js";
  6. /**
  7. * Reusable 3D Scatter Plot Chart
  8. *
  9. * @module
  10. *
  11. * @example
  12. * let chartHolder = d3.select("#chartholder");
  13. *
  14. * let myData = [...];
  15. *
  16. * let myChart = d3.x3d.chart.scatterPlot();
  17. *
  18. * chartHolder.datum(myData).call(myChart);
  19. *
  20. * @see https://datavizproject.com/data-type/3d-scatterplot/
  21. */
  22. export default function() {
  23. /* Default Properties */
  24. let width = 500;
  25. let height = 500;
  26. let dimensions = { x: 40, y: 40, z: 40 };
  27. let colors = ["orange"];
  28. let color;
  29. let classed = "d3X3dScatterPlot";
  30. let mappings;
  31. let debug = false;
  32. /* Scales */
  33. let xScale;
  34. let yScale;
  35. let zScale;
  36. let colorScale;
  37. let sizeScale;
  38. let sizeRange = [0.2];
  39. /* Components */
  40. const viewpoint = component.viewpoint();
  41. const axis = component.axisThreePlane();
  42. const crosshair = component.crosshair();
  43. const label = component.label();
  44. const bubbles = component.bubbles();
  45. /**
  46. * Initialise Data and Scales
  47. *
  48. * @private
  49. * @param {Array} data - Chart data.
  50. */
  51. const init = function(data) {
  52. let newData = {};
  53. ['x', 'y', 'z', 'size', 'color'].forEach((dimension) => {
  54. let set = {
  55. key: dimension,
  56. values: []
  57. };
  58. data.values.forEach((d) => {
  59. let key = mappings[dimension];
  60. let value = d.values.find((v) => v.key === key).value;
  61. set.values.push({ key: key, value: value });
  62. });
  63. newData[dimension] = dataTransform(set).summary();
  64. });
  65. let extentX = newData.x.valueExtent;
  66. let extentY = newData.y.valueExtent;
  67. let extentZ = newData.z.valueExtent;
  68. let extentSize = newData.size.valueExtent;
  69. let extentColor = newData.color.valueExtent;
  70. xScale = d3.scaleLinear()
  71. .domain(extentX)
  72. .range([0, dimensions.x]);
  73. yScale = d3.scaleLinear()
  74. .domain(extentY)
  75. .range([0, dimensions.y]);
  76. zScale = d3.scaleLinear()
  77. .domain(extentZ)
  78. .range([0, dimensions.z]);
  79. sizeScale = d3.scaleLinear()
  80. .domain(extentSize)
  81. .range(sizeRange);
  82. if (color) {
  83. colorScale = d3.scaleQuantize()
  84. .domain(extentColor)
  85. .range([color, color]);
  86. } else {
  87. colorScale = d3.scaleQuantize()
  88. .domain(extentColor)
  89. .range(colors);
  90. }
  91. };
  92. /**
  93. * Constructor
  94. *
  95. * @constructor
  96. * @alias scatterPlot
  97. * @param {d3.selection} selection - The chart holder D3 selection.
  98. */
  99. const my = function(selection) {
  100. const layers = ["axis", "bubbles", "crosshair", "label"];
  101. const scene = createScene(selection, layers, classed, width, height, debug);
  102. selection.each((data) => {
  103. init(data);
  104. // Add Viewpoint
  105. viewpoint.centerOfRotation([dimensions.x / 2, dimensions.y / 2, dimensions.z / 2]);
  106. scene.call(viewpoint);
  107. // Add Axis
  108. axis.xScale(xScale)
  109. .yScale(yScale)
  110. .zScale(zScale);
  111. scene.select(".axis")
  112. .call(axis);
  113. // Add Crosshair
  114. crosshair.xScale(xScale)
  115. .yScale(yScale)
  116. .zScale(zScale);
  117. // Add Labels
  118. label.xScale(xScale)
  119. .yScale(yScale)
  120. .zScale(zScale)
  121. .offset(0.5);
  122. // Add Bubbles
  123. bubbles.xScale(xScale)
  124. .yScale(yScale)
  125. .zScale(zScale)
  126. .sizeScale(sizeScale)
  127. .colorScale(colorScale)
  128. .mappings(mappings)
  129. .on("d3X3dClick", function(e) {
  130. const datum = d3.select(e.target).datum();
  131. let xVal = datum.values.find((v) => v.key === "x").value;
  132. let yVal = datum.values.find((v) => v.key === "y").value;
  133. let zVal = datum.values.find((v) => v.key === "z").value;
  134. const d = { x: xVal, y: yVal, z: zVal };
  135. scene.select(".crosshair")
  136. .datum(d)
  137. .each(function() {
  138. d3.select(this).call(crosshair);
  139. });
  140. })
  141. .on("d3X3dMouseOver", function(e) {
  142. const datum = d3.select(e.target).datum();
  143. let xVal = datum.values.find((v) => v.key === "x").value;
  144. let yVal = datum.values.find((v) => v.key === "y").value;
  145. let zVal = datum.values.find((v) => v.key === "z").value;
  146. const d = { x: xVal, y: yVal, z: zVal, key: datum.key };
  147. scene.select(".label")
  148. .datum(d)
  149. .each(function() {
  150. d3.select(this).call(label);
  151. });
  152. })
  153. .on("d3X3dMouseOut", function(e) {
  154. scene.select(".label")
  155. .selectAll("*")
  156. .remove();
  157. });
  158. scene.select(".bubbles")
  159. .datum(data)
  160. .call(bubbles);
  161. });
  162. };
  163. /**
  164. * Width Getter / Setter
  165. *
  166. * @param {number} _v - X3D canvas width in px.
  167. * @returns {*}
  168. */
  169. my.width = function(_v) {
  170. if (!arguments.length) return width;
  171. width = _v;
  172. return this;
  173. };
  174. /**
  175. * Height Getter / Setter
  176. *
  177. * @param {number} _v - X3D canvas height in px.
  178. * @returns {*}
  179. */
  180. my.height = function(_v) {
  181. if (!arguments.length) return height;
  182. height = _v;
  183. return this;
  184. };
  185. /**
  186. * Dimensions Getter / Setter
  187. *
  188. * @param {{x: number, y: number, z: number}} _v - 3D object dimensions.
  189. * @returns {*}
  190. */
  191. my.dimensions = function(_v) {
  192. if (!arguments.length) return dimensions;
  193. dimensions = _v;
  194. return this;
  195. };
  196. /**
  197. * X Scale Getter / Setter
  198. *
  199. * @param {d3.scale} _v - D3 scale.
  200. * @returns {*}
  201. */
  202. my.xScale = function(_v) {
  203. if (!arguments.length) return xScale;
  204. xScale = _v;
  205. return my;
  206. };
  207. /**
  208. * Y Scale Getter / Setter
  209. *
  210. * @param {d3.scale} _v - D3 scale.
  211. * @returns {*}
  212. */
  213. my.yScale = function(_v) {
  214. if (!arguments.length) return yScale;
  215. yScale = _v;
  216. return my;
  217. };
  218. /**
  219. * Z Scale Getter / Setter
  220. *
  221. * @param {d3.scale} _v - D3 scale.
  222. * @returns {*}
  223. */
  224. my.zScale = function(_v) {
  225. if (!arguments.length) return zScale;
  226. zScale = _v;
  227. return my;
  228. };
  229. /**
  230. * Size Scale Getter / Setter
  231. *
  232. * @param {d3.scale} _v - D3 color scale.
  233. * @returns {*}
  234. */
  235. my.sizeScale = function(_v) {
  236. if (!arguments.length) return sizeScale;
  237. sizeScale = _v;
  238. return my;
  239. };
  240. /**
  241. * Size Range Getter / Setter
  242. *
  243. * @param {number[]} _v - Size min and max (e.g. [1, 9]).
  244. * @returns {*}
  245. */
  246. my.sizeRange = function(_v) {
  247. if (!arguments.length) return sizeRange;
  248. sizeRange = _v;
  249. return my;
  250. };
  251. /**
  252. * Color Scale Getter / Setter
  253. *
  254. * @param {d3.scale} _v - D3 color scale.
  255. * @returns {*}
  256. */
  257. my.colorScale = function(_v) {
  258. if (!arguments.length) return colorScale;
  259. colorScale = _v;
  260. return my;
  261. };
  262. /**
  263. * Color Getter / Setter
  264. *
  265. * @param {string} _v - Color (e.g. "red" or "#ff0000").
  266. * @returns {*}
  267. */
  268. my.color = function(_v) {
  269. if (!arguments.length) return color;
  270. color = _v;
  271. return my;
  272. };
  273. /**
  274. * Colors Getter / Setter
  275. *
  276. * @param {Array} _v - Array of colours used by color scale.
  277. * @returns {*}
  278. */
  279. my.colors = function(_v) {
  280. if (!arguments.length) return colors;
  281. colors = _v;
  282. return my;
  283. };
  284. /**
  285. * Size Scale Getter / Setter
  286. *
  287. * @param {d3.scale} _v - D3 color scale.
  288. * @returns {*}
  289. */
  290. my.sizeScale = function(_v) {
  291. if (!arguments.length) return sizeScale;
  292. sizeScale = _v;
  293. return my;
  294. };
  295. /**
  296. * Size Range Getter / Setter
  297. *
  298. * @param {number[]} _v - Size min and max (e.g. [0.5, 3.0]).
  299. * @returns {*}
  300. */
  301. my.sizeRange = function(_v) {
  302. if (!arguments.length) return sizeRange;
  303. sizeRange = _v;
  304. return my;
  305. };
  306. /**
  307. * Mappings Getter / Setter
  308. *
  309. * @param {Object} _v - Map properties to size, colour etc.
  310. * @returns {*}
  311. */
  312. my.mappings = function(_v) {
  313. if (!arguments.length) return mappings;
  314. mappings = _v;
  315. return my;
  316. };
  317. /**
  318. * Debug Getter / Setter
  319. *
  320. * @param {boolean} _v - Show debug log and stats. True/False.
  321. * @returns {*}
  322. */
  323. my.debug = function(_v) {
  324. if (!arguments.length) return debug;
  325. debug = _v;
  326. return my;
  327. };
  328. /**
  329. * Dispatch On Getter
  330. *
  331. * @returns {*}
  332. */
  333. my.on = function() {
  334. let value = dispatch.on.apply(dispatch, arguments);
  335. return value === dispatch ? my : value;
  336. };
  337. return my;
  338. }