選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 

447 行
11 KiB

  1. import { makeSVGGroup } from '../utils/draw';
  2. import { makeText, makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, percentageBar, getPaths, heatSquare } from '../utils/draw';
  3. import { equilizeNoOfElements } from '../utils/draw-utils';
  4. import { translateHoriLine, translateVertLine, animateRegion, animateBar,
  5. animateDot, animatePath, animatePathStr } from '../utils/animate';
  6. import { getMonthName } from '../utils/date-utils';
  7. class ChartComponent {
  8. constructor({
  9. layerClass = '',
  10. layerTransform = '',
  11. constants,
  12. getData,
  13. makeElements,
  14. animateElements
  15. }) {
  16. this.layerTransform = layerTransform;
  17. this.constants = constants;
  18. this.makeElements = makeElements;
  19. this.getData = getData;
  20. this.animateElements = animateElements;
  21. this.store = [];
  22. this.labels = [];
  23. this.layerClass = layerClass;
  24. this.layerClass = typeof(this.layerClass) === 'function'
  25. ? this.layerClass() : this.layerClass;
  26. this.refresh();
  27. }
  28. refresh(data) {
  29. this.data = data || this.getData();
  30. }
  31. setup(parent) {
  32. this.layer = makeSVGGroup(this.layerClass, this.layerTransform, parent);
  33. }
  34. make() {
  35. this.render(this.data);
  36. this.oldData = this.data;
  37. }
  38. render(data) {
  39. this.store = this.makeElements(data);
  40. this.layer.textContent = '';
  41. this.store.forEach(element => {
  42. this.layer.appendChild(element);
  43. });
  44. this.labels.forEach(element => {
  45. this.layer.appendChild(element);
  46. });
  47. }
  48. update(animate = true) {
  49. this.refresh();
  50. let animateElements = [];
  51. if(animate) {
  52. animateElements = this.animateElements(this.data) || [];
  53. }
  54. return animateElements;
  55. }
  56. }
  57. let componentConfigs = {
  58. donutSlices: {
  59. layerClass: 'donut-slices',
  60. makeElements(data) {
  61. return data.sliceStrings.map((s, i) => {
  62. let slice = makePath(s, 'donut-path', data.colors[i], 'none', data.strokeWidth);
  63. slice.style.transition = 'transform .3s;';
  64. return slice;
  65. });
  66. },
  67. animateElements(newData) {
  68. return this.store.map((slice, i) => animatePathStr(slice, newData.sliceStrings[i]));
  69. },
  70. },
  71. pieSlices: {
  72. layerClass: 'pie-slices',
  73. makeElements(data) {
  74. return data.sliceStrings.map((s, i) =>{
  75. let slice = makePath(s, 'pie-path', 'none', data.colors[i]);
  76. slice.style.transition = 'transform .3s;';
  77. return slice;
  78. });
  79. },
  80. animateElements(newData) {
  81. return this.store.map((slice, i) =>
  82. animatePathStr(slice, newData.sliceStrings[i])
  83. );
  84. }
  85. },
  86. percentageBars: {
  87. layerClass: 'percentage-bars',
  88. makeElements(data) {
  89. return data.xPositions.map((x, i) =>{
  90. let y = 0;
  91. let bar = percentageBar(x, y, data.widths[i],
  92. this.constants.barHeight, this.constants.barDepth, data.colors[i]);
  93. return bar;
  94. });
  95. },
  96. animateElements(newData) {
  97. if(newData) return [];
  98. }
  99. },
  100. yAxis: {
  101. layerClass: 'y axis',
  102. makeElements(data) {
  103. return data.positions.map((position, i) =>
  104. yLine(position, data.labels[i], this.constants.width,
  105. {mode: this.constants.mode, pos: this.constants.pos, shortenNumbers: this.constants.shortenNumbers})
  106. );
  107. },
  108. animateElements(newData) {
  109. let newPos = newData.positions;
  110. let newLabels = newData.labels;
  111. let oldPos = this.oldData.positions;
  112. let oldLabels = this.oldData.labels;
  113. [oldPos, newPos] = equilizeNoOfElements(oldPos, newPos);
  114. [oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels);
  115. this.render({
  116. positions: oldPos,
  117. labels: newLabels
  118. });
  119. return this.store.map((line, i) => {
  120. return translateHoriLine(
  121. line, newPos[i], oldPos[i]
  122. );
  123. });
  124. }
  125. },
  126. xAxis: {
  127. layerClass: 'x axis',
  128. makeElements(data) {
  129. return data.positions.map((position, i) =>
  130. xLine(position, data.calcLabels[i], this.constants.height,
  131. {mode: this.constants.mode, pos: this.constants.pos})
  132. );
  133. },
  134. animateElements(newData) {
  135. let newPos = newData.positions;
  136. let newLabels = newData.calcLabels;
  137. let oldPos = this.oldData.positions;
  138. let oldLabels = this.oldData.calcLabels;
  139. [oldPos, newPos] = equilizeNoOfElements(oldPos, newPos);
  140. [oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels);
  141. this.render({
  142. positions: oldPos,
  143. calcLabels: newLabels
  144. });
  145. return this.store.map((line, i) => {
  146. return translateVertLine(
  147. line, newPos[i], oldPos[i]
  148. );
  149. });
  150. }
  151. },
  152. yMarkers: {
  153. layerClass: 'y-markers',
  154. makeElements(data) {
  155. return data.map(m =>
  156. yMarker(m.position, m.label, this.constants.width,
  157. {labelPos: m.options.labelPos, mode: 'span', lineType: 'dashed'})
  158. );
  159. },
  160. animateElements(newData) {
  161. [this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
  162. let newPos = newData.map(d => d.position);
  163. let newLabels = newData.map(d => d.label);
  164. let newOptions = newData.map(d => d.options);
  165. let oldPos = this.oldData.map(d => d.position);
  166. this.render(oldPos.map((pos, i) => {
  167. return {
  168. position: oldPos[i],
  169. label: newLabels[i],
  170. options: newOptions[i]
  171. };
  172. }));
  173. return this.store.map((line, i) => {
  174. return translateHoriLine(
  175. line, newPos[i], oldPos[i]
  176. );
  177. });
  178. }
  179. },
  180. yRegions: {
  181. layerClass: 'y-regions',
  182. makeElements(data) {
  183. return data.map(r =>
  184. yRegion(r.startPos, r.endPos, this.constants.width,
  185. r.label, {labelPos: r.options.labelPos})
  186. );
  187. },
  188. animateElements(newData) {
  189. [this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
  190. let newPos = newData.map(d => d.endPos);
  191. let newLabels = newData.map(d => d.label);
  192. let newStarts = newData.map(d => d.startPos);
  193. let newOptions = newData.map(d => d.options);
  194. let oldPos = this.oldData.map(d => d.endPos);
  195. let oldStarts = this.oldData.map(d => d.startPos);
  196. this.render(oldPos.map((pos, i) => {
  197. return {
  198. startPos: oldStarts[i],
  199. endPos: oldPos[i],
  200. label: newLabels[i],
  201. options: newOptions[i]
  202. };
  203. }));
  204. let animateElements = [];
  205. this.store.map((rectGroup, i) => {
  206. animateElements = animateElements.concat(animateRegion(
  207. rectGroup, newStarts[i], newPos[i], oldPos[i]
  208. ));
  209. });
  210. return animateElements;
  211. }
  212. },
  213. heatDomain: {
  214. layerClass: function() { return 'heat-domain domain-' + this.constants.index; },
  215. makeElements(data) {
  216. let {index, colWidth, rowHeight, squareSize, radius, xTranslate} = this.constants;
  217. let monthNameHeight = -12;
  218. let x = xTranslate, y = 0;
  219. this.serializedSubDomains = [];
  220. data.cols.map((week, weekNo) => {
  221. if(weekNo === 1) {
  222. this.labels.push(
  223. makeText('domain-name', x, monthNameHeight, getMonthName(index, true).toUpperCase(),
  224. {
  225. fontSize: 9
  226. }
  227. )
  228. );
  229. }
  230. week.map((day, i) => {
  231. if(day.fill) {
  232. let data = {
  233. 'data-date': day.yyyyMmDd,
  234. 'data-value': day.dataValue,
  235. 'data-day': i
  236. };
  237. let square = heatSquare('day', x, y, squareSize, radius, day.fill, data);
  238. this.serializedSubDomains.push(square);
  239. }
  240. y += rowHeight;
  241. });
  242. y = 0;
  243. x += colWidth;
  244. });
  245. return this.serializedSubDomains;
  246. },
  247. animateElements(newData) {
  248. if(newData) return [];
  249. }
  250. },
  251. barGraph: {
  252. layerClass: function() { return 'dataset-units dataset-bars dataset-' + this.constants.index; },
  253. makeElements(data) {
  254. let c = this.constants;
  255. this.unitType = 'bar';
  256. this.units = data.yPositions.map((y, j) => {
  257. return datasetBar(
  258. data.xPositions[j],
  259. y,
  260. data.barWidth,
  261. c.color,
  262. data.labels[j],
  263. j,
  264. data.offsets[j],
  265. {
  266. zeroLine: data.zeroLine,
  267. barsWidth: data.barsWidth,
  268. minHeight: c.minHeight
  269. }
  270. );
  271. });
  272. return this.units;
  273. },
  274. animateElements(newData) {
  275. let newXPos = newData.xPositions;
  276. let newYPos = newData.yPositions;
  277. let newOffsets = newData.offsets;
  278. let newLabels = newData.labels;
  279. let oldXPos = this.oldData.xPositions;
  280. let oldYPos = this.oldData.yPositions;
  281. let oldOffsets = this.oldData.offsets;
  282. let oldLabels = this.oldData.labels;
  283. [oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos);
  284. [oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos);
  285. [oldOffsets, newOffsets] = equilizeNoOfElements(oldOffsets, newOffsets);
  286. [oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels);
  287. this.render({
  288. xPositions: oldXPos,
  289. yPositions: oldYPos,
  290. offsets: oldOffsets,
  291. labels: newLabels,
  292. zeroLine: this.oldData.zeroLine,
  293. barsWidth: this.oldData.barsWidth,
  294. barWidth: this.oldData.barWidth,
  295. });
  296. let animateElements = [];
  297. this.store.map((bar, i) => {
  298. animateElements = animateElements.concat(animateBar(
  299. bar, newXPos[i], newYPos[i], newData.barWidth, newOffsets[i],
  300. {zeroLine: newData.zeroLine}
  301. ));
  302. });
  303. return animateElements;
  304. }
  305. },
  306. lineGraph: {
  307. layerClass: function() { return 'dataset-units dataset-line dataset-' + this.constants.index; },
  308. makeElements(data) {
  309. let c = this.constants;
  310. this.unitType = 'dot';
  311. this.paths = {};
  312. if(!c.hideLine) {
  313. this.paths = getPaths(
  314. data.xPositions,
  315. data.yPositions,
  316. c.color,
  317. {
  318. heatline: c.heatline,
  319. regionFill: c.regionFill,
  320. spline: c.spline
  321. },
  322. {
  323. svgDefs: c.svgDefs,
  324. zeroLine: data.zeroLine
  325. }
  326. );
  327. }
  328. this.units = [];
  329. if(!c.hideDots) {
  330. this.units = data.yPositions.map((y, j) => {
  331. return datasetDot(
  332. data.xPositions[j],
  333. y,
  334. data.radius,
  335. c.color,
  336. (c.valuesOverPoints ? data.values[j] : ''),
  337. j
  338. );
  339. });
  340. }
  341. return Object.values(this.paths).concat(this.units);
  342. },
  343. animateElements(newData) {
  344. let newXPos = newData.xPositions;
  345. let newYPos = newData.yPositions;
  346. let newValues = newData.values;
  347. let oldXPos = this.oldData.xPositions;
  348. let oldYPos = this.oldData.yPositions;
  349. let oldValues = this.oldData.values;
  350. [oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos);
  351. [oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos);
  352. [oldValues, newValues] = equilizeNoOfElements(oldValues, newValues);
  353. this.render({
  354. xPositions: oldXPos,
  355. yPositions: oldYPos,
  356. values: newValues,
  357. zeroLine: this.oldData.zeroLine,
  358. radius: this.oldData.radius,
  359. });
  360. let animateElements = [];
  361. if(Object.keys(this.paths).length) {
  362. animateElements = animateElements.concat(animatePath(
  363. this.paths, newXPos, newYPos, newData.zeroLine, this.constants.spline));
  364. }
  365. if(this.units.length) {
  366. this.units.map((dot, i) => {
  367. animateElements = animateElements.concat(animateDot(
  368. dot, newXPos[i], newYPos[i]));
  369. });
  370. }
  371. return animateElements;
  372. }
  373. }
  374. };
  375. export function getComponent(name, constants, getData) {
  376. let keys = Object.keys(componentConfigs).filter(k => name.includes(k));
  377. let config = componentConfigs[keys[0]];
  378. Object.assign(config, {
  379. constants: constants,
  380. getData: getData
  381. });
  382. return new ChartComponent(config);
  383. }