ScatterChart.mjs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /**
  2. * @author tlwr [toby@toby.codes]
  3. * @copyright Crown Copyright 2019
  4. * @license Apache-2.0
  5. */
  6. import * as d3 from "d3";
  7. import { getScatterValues, getScatterValuesWithColour, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts";
  8. import Operation from "../Operation";
  9. import Utils from "../Utils";
  10. /**
  11. * Scatter chart operation
  12. */
  13. class ScatterChart extends Operation {
  14. /**
  15. * ScatterChart constructor
  16. */
  17. constructor() {
  18. super();
  19. this.name = "Scatter chart";
  20. this.module = "Charts";
  21. this.description = "";
  22. this.infoURL = "";
  23. this.inputType = "string";
  24. this.outputType = "html";
  25. this.args = [
  26. {
  27. name: "Record delimiter",
  28. type: "option",
  29. value: RECORD_DELIMITER_OPTIONS,
  30. },
  31. {
  32. name: "Field delimiter",
  33. type: "option",
  34. value: FIELD_DELIMITER_OPTIONS,
  35. },
  36. {
  37. name: "Use column headers as labels",
  38. type: "boolean",
  39. value: true,
  40. },
  41. {
  42. name: "X label",
  43. type: "string",
  44. value: "",
  45. },
  46. {
  47. name: "Y label",
  48. type: "string",
  49. value: "",
  50. },
  51. {
  52. name: "Colour",
  53. type: "string",
  54. value: COLOURS.max,
  55. },
  56. {
  57. name: "Point radius",
  58. type: "number",
  59. value: 10,
  60. },
  61. {
  62. name: "Use colour from third column",
  63. type: "boolean",
  64. value: false,
  65. }
  66. ];
  67. }
  68. /**
  69. * @param {string} input
  70. * @param {Object[]} args
  71. * @returns {html}
  72. */
  73. run(input, args) {
  74. const recordDelimiter = Utils.charRep[args[0]],
  75. fieldDelimiter = Utils.charRep[args[1]],
  76. columnHeadingsAreIncluded = args[2],
  77. fillColour = args[5],
  78. radius = args[6],
  79. colourInInput = args[7],
  80. dimension = 500;
  81. let xLabel = args[3],
  82. yLabel = args[4];
  83. const dataFunction = colourInInput ? getScatterValuesWithColour : getScatterValues;
  84. const { headings, values } = dataFunction(
  85. input,
  86. recordDelimiter,
  87. fieldDelimiter,
  88. columnHeadingsAreIncluded
  89. );
  90. if (headings) {
  91. xLabel = headings.x;
  92. yLabel = headings.y;
  93. }
  94. let svg = document.createElement("svg");
  95. svg = d3.select(svg)
  96. .attr("width", "100%")
  97. .attr("height", "100%")
  98. .attr("viewBox", `0 0 ${dimension} ${dimension}`);
  99. const margin = {
  100. top: 10,
  101. right: 0,
  102. bottom: 40,
  103. left: 30,
  104. },
  105. width = dimension - margin.left - margin.right,
  106. height = dimension - margin.top - margin.bottom,
  107. marginedSpace = svg.append("g")
  108. .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
  109. const xExtent = d3.extent(values, d => d[0]),
  110. xDelta = xExtent[1] - xExtent[0],
  111. yExtent = d3.extent(values, d => d[1]),
  112. yDelta = yExtent[1] - yExtent[0],
  113. xAxis = d3.scaleLinear()
  114. .domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)])
  115. .range([0, width]),
  116. yAxis = d3.scaleLinear()
  117. .domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)])
  118. .range([height, 0]);
  119. marginedSpace.append("clipPath")
  120. .attr("id", "clip")
  121. .append("rect")
  122. .attr("width", width)
  123. .attr("height", height);
  124. marginedSpace.append("g")
  125. .attr("class", "points")
  126. .attr("clip-path", "url(#clip)")
  127. .selectAll("circle")
  128. .data(values)
  129. .enter()
  130. .append("circle")
  131. .attr("cx", (d) => xAxis(d[0]))
  132. .attr("cy", (d) => yAxis(d[1]))
  133. .attr("r", d => radius)
  134. .attr("fill", d => {
  135. return colourInInput ? d[2] : fillColour;
  136. })
  137. .attr("stroke", "rgba(0, 0, 0, 0.5)")
  138. .attr("stroke-width", "0.5")
  139. .append("title")
  140. .text(d => {
  141. const x = d[0],
  142. y = d[1],
  143. tooltip = `X: ${x}\n
  144. Y: ${y}\n
  145. `.replace(/\s{2,}/g, "\n");
  146. return tooltip;
  147. });
  148. marginedSpace.append("g")
  149. .attr("class", "axis axis--y")
  150. .call(d3.axisLeft(yAxis).tickSizeOuter(-width));
  151. svg.append("text")
  152. .attr("transform", "rotate(-90)")
  153. .attr("y", -margin.left)
  154. .attr("x", -(height / 2))
  155. .attr("dy", "1em")
  156. .style("text-anchor", "middle")
  157. .text(yLabel);
  158. marginedSpace.append("g")
  159. .attr("class", "axis axis--x")
  160. .attr("transform", "translate(0," + height + ")")
  161. .call(d3.axisBottom(xAxis).tickSizeOuter(-height));
  162. svg.append("text")
  163. .attr("x", width / 2)
  164. .attr("y", dimension)
  165. .style("text-anchor", "middle")
  166. .text(xLabel);
  167. return svg._groups[0][0].outerHTML;
  168. }
  169. }
  170. export default ScatterChart;