shape.line.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. import CLASS from './class'
  2. import { ChartInternal } from './core'
  3. import { isValue, isFunction, isUndefined, isDefined } from './util'
  4. ChartInternal.prototype.initLine = function() {
  5. var $$ = this
  6. $$.main
  7. .select('.' + CLASS.chart)
  8. .append('g')
  9. .attr('class', CLASS.chartLines)
  10. }
  11. ChartInternal.prototype.updateTargetsForLine = function(targets) {
  12. var $$ = this,
  13. config = $$.config,
  14. mainLines,
  15. mainLineEnter,
  16. classChartLine = $$.classChartLine.bind($$),
  17. classLines = $$.classLines.bind($$),
  18. classAreas = $$.classAreas.bind($$),
  19. classCircles = $$.classCircles.bind($$),
  20. classFocus = $$.classFocus.bind($$)
  21. mainLines = $$.main
  22. .select('.' + CLASS.chartLines)
  23. .selectAll('.' + CLASS.chartLine)
  24. .data(targets)
  25. .attr('class', function(d) {
  26. return classChartLine(d) + classFocus(d)
  27. })
  28. mainLineEnter = mainLines
  29. .enter()
  30. .append('g')
  31. .attr('class', classChartLine)
  32. .style('opacity', 0)
  33. .style('pointer-events', 'none')
  34. // Lines for each data
  35. mainLineEnter.append('g').attr('class', classLines)
  36. // Areas
  37. mainLineEnter.append('g').attr('class', classAreas)
  38. // Circles for each data point on lines
  39. mainLineEnter.append('g').attr('class', function(d) {
  40. return $$.generateClass(CLASS.selectedCircles, d.id)
  41. })
  42. mainLineEnter
  43. .append('g')
  44. .attr('class', classCircles)
  45. .style('cursor', function(d) {
  46. return config.data_selection_isselectable(d) ? 'pointer' : null
  47. })
  48. // Update date for selected circles
  49. targets.forEach(function(t) {
  50. $$.main
  51. .selectAll('.' + CLASS.selectedCircles + $$.getTargetSelectorSuffix(t.id))
  52. .selectAll('.' + CLASS.selectedCircle)
  53. .each(function(d) {
  54. d.value = t.values[d.index].value
  55. })
  56. })
  57. // MEMO: can not keep same color...
  58. //mainLineUpdate.exit().remove();
  59. }
  60. ChartInternal.prototype.updateLine = function(durationForExit) {
  61. var $$ = this
  62. var mainLine = $$.main
  63. .selectAll('.' + CLASS.lines)
  64. .selectAll('.' + CLASS.line)
  65. .data($$.lineData.bind($$))
  66. var mainLineEnter = mainLine
  67. .enter()
  68. .append('path')
  69. .attr('class', $$.classLine.bind($$))
  70. .style('stroke', $$.color)
  71. $$.mainLine = mainLineEnter
  72. .merge(mainLine)
  73. .style('opacity', $$.initialOpacity.bind($$))
  74. .style('shape-rendering', function(d) {
  75. return $$.isStepType(d) ? 'crispEdges' : ''
  76. })
  77. .attr('transform', null)
  78. mainLine
  79. .exit()
  80. .transition()
  81. .duration(durationForExit)
  82. .style('opacity', 0)
  83. }
  84. ChartInternal.prototype.redrawLine = function(
  85. drawLine,
  86. withTransition,
  87. transition
  88. ) {
  89. return [
  90. (withTransition ? this.mainLine.transition(transition) : this.mainLine)
  91. .attr('d', drawLine)
  92. .style('stroke', this.color)
  93. .style('opacity', 1)
  94. ]
  95. }
  96. ChartInternal.prototype.generateDrawLine = function(lineIndices, isSub) {
  97. var $$ = this,
  98. config = $$.config,
  99. line = $$.d3.line(),
  100. getPoints = $$.generateGetLinePoints(lineIndices, isSub),
  101. yScaleGetter = isSub ? $$.getSubYScale : $$.getYScale,
  102. xValue = function(d) {
  103. return (isSub ? $$.subxx : $$.xx).call($$, d)
  104. },
  105. yValue = function(d, i) {
  106. return config.data_groups.length > 0
  107. ? getPoints(d, i)[0][1]
  108. : yScaleGetter.call($$, d.id)(d.value)
  109. }
  110. line = config.axis_rotated
  111. ? line.x(yValue).y(xValue)
  112. : line.x(xValue).y(yValue)
  113. if (!config.line_connectNull) {
  114. line = line.defined(function(d) {
  115. return d.value != null
  116. })
  117. }
  118. return function(d) {
  119. var values = config.line_connectNull
  120. ? $$.filterRemoveNull(d.values)
  121. : d.values,
  122. x = isSub ? $$.subX : $$.x,
  123. y = yScaleGetter.call($$, d.id),
  124. x0 = 0,
  125. y0 = 0,
  126. path
  127. if ($$.isLineType(d)) {
  128. if (config.data_regions[d.id]) {
  129. path = $$.lineWithRegions(values, x, y, config.data_regions[d.id])
  130. } else {
  131. if ($$.isStepType(d)) {
  132. values = $$.convertValuesToStep(values)
  133. }
  134. path = line.curve($$.getInterpolate(d))(values)
  135. }
  136. } else {
  137. if (values[0]) {
  138. x0 = x(values[0].x)
  139. y0 = y(values[0].value)
  140. }
  141. path = config.axis_rotated ? 'M ' + y0 + ' ' + x0 : 'M ' + x0 + ' ' + y0
  142. }
  143. return path ? path : 'M 0 0'
  144. }
  145. }
  146. ChartInternal.prototype.generateGetLinePoints = function(lineIndices, isSub) {
  147. // partial duplication of generateGetBarPoints
  148. var $$ = this,
  149. config = $$.config,
  150. lineTargetsNum = lineIndices.__max__ + 1,
  151. x = $$.getShapeX(0, lineTargetsNum, lineIndices, !!isSub),
  152. y = $$.getShapeY(!!isSub),
  153. lineOffset = $$.getShapeOffset($$.isLineType, lineIndices, !!isSub),
  154. yScale = isSub ? $$.getSubYScale : $$.getYScale
  155. return function(d, i) {
  156. var y0 = yScale.call($$, d.id)(0),
  157. offset = lineOffset(d, i) || y0, // offset is for stacked area chart
  158. posX = x(d),
  159. posY = y(d)
  160. // fix posY not to overflow opposite quadrant
  161. if (config.axis_rotated) {
  162. if ((0 < d.value && posY < y0) || (d.value < 0 && y0 < posY)) {
  163. posY = y0
  164. }
  165. }
  166. // 1 point that marks the line position
  167. return [
  168. [posX, posY - (y0 - offset)],
  169. [posX, posY - (y0 - offset)], // needed for compatibility
  170. [posX, posY - (y0 - offset)], // needed for compatibility
  171. [posX, posY - (y0 - offset)] // needed for compatibility
  172. ]
  173. }
  174. }
  175. ChartInternal.prototype.lineWithRegions = function(d, x, y, _regions) {
  176. var $$ = this,
  177. config = $$.config,
  178. prev = -1,
  179. i,
  180. j,
  181. s = 'M',
  182. sWithRegion,
  183. xp,
  184. yp,
  185. dx,
  186. dy,
  187. dd,
  188. diff,
  189. diffx2,
  190. xOffset = $$.isCategorized() ? 0.5 : 0,
  191. xValue,
  192. yValue,
  193. regions = []
  194. function isWithinRegions(x, regions) {
  195. var i
  196. for (i = 0; i < regions.length; i++) {
  197. if (regions[i].start < x && x <= regions[i].end) {
  198. return true
  199. }
  200. }
  201. return false
  202. }
  203. // Check start/end of regions
  204. if (isDefined(_regions)) {
  205. for (i = 0; i < _regions.length; i++) {
  206. regions[i] = {}
  207. if (isUndefined(_regions[i].start)) {
  208. regions[i].start = d[0].x
  209. } else {
  210. regions[i].start = $$.isTimeSeries()
  211. ? $$.parseDate(_regions[i].start)
  212. : _regions[i].start
  213. }
  214. if (isUndefined(_regions[i].end)) {
  215. regions[i].end = d[d.length - 1].x
  216. } else {
  217. regions[i].end = $$.isTimeSeries()
  218. ? $$.parseDate(_regions[i].end)
  219. : _regions[i].end
  220. }
  221. }
  222. }
  223. // Set scales
  224. xValue = config.axis_rotated
  225. ? function(d) {
  226. return y(d.value)
  227. }
  228. : function(d) {
  229. return x(d.x)
  230. }
  231. yValue = config.axis_rotated
  232. ? function(d) {
  233. return x(d.x)
  234. }
  235. : function(d) {
  236. return y(d.value)
  237. }
  238. // Define svg generator function for region
  239. function generateM(points) {
  240. return (
  241. 'M' +
  242. points[0][0] +
  243. ' ' +
  244. points[0][1] +
  245. ' ' +
  246. points[1][0] +
  247. ' ' +
  248. points[1][1]
  249. )
  250. }
  251. if ($$.isTimeSeries()) {
  252. sWithRegion = function(d0, d1, j, diff) {
  253. var x0 = d0.x.getTime(),
  254. x_diff = d1.x - d0.x,
  255. xv0 = new Date(x0 + x_diff * j),
  256. xv1 = new Date(x0 + x_diff * (j + diff)),
  257. points
  258. if (config.axis_rotated) {
  259. points = [
  260. [y(yp(j)), x(xv0)],
  261. [y(yp(j + diff)), x(xv1)]
  262. ]
  263. } else {
  264. points = [
  265. [x(xv0), y(yp(j))],
  266. [x(xv1), y(yp(j + diff))]
  267. ]
  268. }
  269. return generateM(points)
  270. }
  271. } else {
  272. sWithRegion = function(d0, d1, j, diff) {
  273. var points
  274. if (config.axis_rotated) {
  275. points = [
  276. [y(yp(j), true), x(xp(j))],
  277. [y(yp(j + diff), true), x(xp(j + diff))]
  278. ]
  279. } else {
  280. points = [
  281. [x(xp(j), true), y(yp(j))],
  282. [x(xp(j + diff), true), y(yp(j + diff))]
  283. ]
  284. }
  285. return generateM(points)
  286. }
  287. }
  288. // Generate
  289. for (i = 0; i < d.length; i++) {
  290. // Draw as normal
  291. if (isUndefined(regions) || !isWithinRegions(d[i].x, regions)) {
  292. s += ' ' + xValue(d[i]) + ' ' + yValue(d[i])
  293. }
  294. // Draw with region // TODO: Fix for horizotal charts
  295. else {
  296. xp = $$.getScale(
  297. d[i - 1].x + xOffset,
  298. d[i].x + xOffset,
  299. $$.isTimeSeries()
  300. )
  301. yp = $$.getScale(d[i - 1].value, d[i].value)
  302. dx = x(d[i].x) - x(d[i - 1].x)
  303. dy = y(d[i].value) - y(d[i - 1].value)
  304. dd = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2))
  305. diff = 2 / dd
  306. diffx2 = diff * 2
  307. for (j = diff; j <= 1; j += diffx2) {
  308. s += sWithRegion(d[i - 1], d[i], j, diff)
  309. }
  310. }
  311. prev = d[i].x
  312. }
  313. return s
  314. }
  315. ChartInternal.prototype.updateArea = function(durationForExit) {
  316. var $$ = this,
  317. d3 = $$.d3
  318. var mainArea = $$.main
  319. .selectAll('.' + CLASS.areas)
  320. .selectAll('.' + CLASS.area)
  321. .data($$.lineData.bind($$))
  322. var mainAreaEnter = mainArea
  323. .enter()
  324. .append('path')
  325. .attr('class', $$.classArea.bind($$))
  326. .style('fill', $$.color)
  327. .style('opacity', function() {
  328. $$.orgAreaOpacity = +d3.select(this).style('opacity')
  329. return 0
  330. })
  331. $$.mainArea = mainAreaEnter
  332. .merge(mainArea)
  333. .style('opacity', $$.orgAreaOpacity)
  334. mainArea
  335. .exit()
  336. .transition()
  337. .duration(durationForExit)
  338. .style('opacity', 0)
  339. }
  340. ChartInternal.prototype.redrawArea = function(
  341. drawArea,
  342. withTransition,
  343. transition
  344. ) {
  345. return [
  346. (withTransition ? this.mainArea.transition(transition) : this.mainArea)
  347. .attr('d', drawArea)
  348. .style('fill', this.color)
  349. .style('opacity', this.orgAreaOpacity)
  350. ]
  351. }
  352. ChartInternal.prototype.generateDrawArea = function(areaIndices, isSub) {
  353. var $$ = this,
  354. config = $$.config,
  355. area = $$.d3.area(),
  356. getPoints = $$.generateGetAreaPoints(areaIndices, isSub),
  357. yScaleGetter = isSub ? $$.getSubYScale : $$.getYScale,
  358. xValue = function(d) {
  359. return (isSub ? $$.subxx : $$.xx).call($$, d)
  360. },
  361. value0 = function(d, i) {
  362. return config.data_groups.length > 0
  363. ? getPoints(d, i)[0][1]
  364. : yScaleGetter.call($$, d.id)($$.getAreaBaseValue(d.id))
  365. },
  366. value1 = function(d, i) {
  367. return config.data_groups.length > 0
  368. ? getPoints(d, i)[1][1]
  369. : yScaleGetter.call($$, d.id)(d.value)
  370. }
  371. area = config.axis_rotated
  372. ? area
  373. .x0(value0)
  374. .x1(value1)
  375. .y(xValue)
  376. : area
  377. .x(xValue)
  378. .y0(config.area_above ? 0 : value0)
  379. .y1(value1)
  380. if (!config.line_connectNull) {
  381. area = area.defined(function(d) {
  382. return d.value !== null
  383. })
  384. }
  385. return function(d) {
  386. var values = config.line_connectNull
  387. ? $$.filterRemoveNull(d.values)
  388. : d.values,
  389. x0 = 0,
  390. y0 = 0,
  391. path
  392. if ($$.isAreaType(d)) {
  393. if ($$.isStepType(d)) {
  394. values = $$.convertValuesToStep(values)
  395. }
  396. path = area.curve($$.getInterpolate(d))(values)
  397. } else {
  398. if (values[0]) {
  399. x0 = $$.x(values[0].x)
  400. y0 = $$.getYScale(d.id)(values[0].value)
  401. }
  402. path = config.axis_rotated ? 'M ' + y0 + ' ' + x0 : 'M ' + x0 + ' ' + y0
  403. }
  404. return path ? path : 'M 0 0'
  405. }
  406. }
  407. ChartInternal.prototype.getAreaBaseValue = function() {
  408. return 0
  409. }
  410. ChartInternal.prototype.generateGetAreaPoints = function(areaIndices, isSub) {
  411. // partial duplication of generateGetBarPoints
  412. var $$ = this,
  413. config = $$.config,
  414. areaTargetsNum = areaIndices.__max__ + 1,
  415. x = $$.getShapeX(0, areaTargetsNum, areaIndices, !!isSub),
  416. y = $$.getShapeY(!!isSub),
  417. areaOffset = $$.getShapeOffset($$.isAreaType, areaIndices, !!isSub),
  418. yScale = isSub ? $$.getSubYScale : $$.getYScale
  419. return function(d, i) {
  420. var y0 = yScale.call($$, d.id)(0),
  421. offset = areaOffset(d, i) || y0, // offset is for stacked area chart
  422. posX = x(d),
  423. posY = y(d)
  424. // fix posY not to overflow opposite quadrant
  425. if (config.axis_rotated) {
  426. if ((0 < d.value && posY < y0) || (d.value < 0 && y0 < posY)) {
  427. posY = y0
  428. }
  429. }
  430. // 1 point that marks the area position
  431. return [
  432. [posX, offset],
  433. [posX, posY - (y0 - offset)],
  434. [posX, posY - (y0 - offset)], // needed for compatibility
  435. [posX, offset] // needed for compatibility
  436. ]
  437. }
  438. }
  439. ChartInternal.prototype.updateCircle = function(cx, cy) {
  440. var $$ = this
  441. var mainCircle = $$.main
  442. .selectAll('.' + CLASS.circles)
  443. .selectAll('.' + CLASS.circle)
  444. .data($$.lineOrScatterOrStanfordData.bind($$))
  445. var mainCircleEnter = mainCircle
  446. .enter()
  447. .append('circle')
  448. .attr('shape-rendering', $$.isStanfordGraphType() ? 'crispEdges' : '')
  449. .attr('class', $$.classCircle.bind($$))
  450. .attr('cx', cx)
  451. .attr('cy', cy)
  452. .attr('r', $$.pointR.bind($$))
  453. .style(
  454. 'color',
  455. $$.isStanfordGraphType() ? $$.getStanfordPointColor.bind($$) : $$.color
  456. )
  457. $$.mainCircle = mainCircleEnter
  458. .merge(mainCircle)
  459. .style(
  460. 'opacity',
  461. $$.isStanfordGraphType() ? 1 : $$.initialOpacityForCircle.bind($$)
  462. )
  463. mainCircle.exit().style('opacity', 0)
  464. }
  465. ChartInternal.prototype.redrawCircle = function(
  466. cx,
  467. cy,
  468. withTransition,
  469. transition
  470. ) {
  471. var $$ = this,
  472. selectedCircles = $$.main.selectAll('.' + CLASS.selectedCircle)
  473. return [
  474. (withTransition ? $$.mainCircle.transition(transition) : $$.mainCircle)
  475. .style('opacity', this.opacityForCircle.bind($$))
  476. .style(
  477. 'color',
  478. $$.isStanfordGraphType() ? $$.getStanfordPointColor.bind($$) : $$.color
  479. )
  480. .attr('cx', cx)
  481. .attr('cy', cy),
  482. (withTransition ? selectedCircles.transition(transition) : selectedCircles)
  483. .attr('cx', cx)
  484. .attr('cy', cy)
  485. ]
  486. }
  487. ChartInternal.prototype.circleX = function(d) {
  488. return d.x || d.x === 0 ? this.x(d.x) : null
  489. }
  490. ChartInternal.prototype.updateCircleY = function() {
  491. var $$ = this,
  492. lineIndices,
  493. getPoints
  494. if ($$.config.data_groups.length > 0) {
  495. ;(lineIndices = $$.getShapeIndices($$.isLineType)),
  496. (getPoints = $$.generateGetLinePoints(lineIndices))
  497. $$.circleY = function(d, i) {
  498. return getPoints(d, i)[0][1]
  499. }
  500. } else {
  501. $$.circleY = function(d) {
  502. return $$.getYScale(d.id)(d.value)
  503. }
  504. }
  505. }
  506. ChartInternal.prototype.getCircles = function(i, id) {
  507. var $$ = this
  508. return (id
  509. ? $$.main.selectAll('.' + CLASS.circles + $$.getTargetSelectorSuffix(id))
  510. : $$.main
  511. ).selectAll('.' + CLASS.circle + (isValue(i) ? '-' + i : ''))
  512. }
  513. ChartInternal.prototype.expandCircles = function(i, id, reset) {
  514. var $$ = this,
  515. r = $$.pointExpandedR.bind($$)
  516. if (reset) {
  517. $$.unexpandCircles()
  518. }
  519. $$.getCircles(i, id)
  520. .classed(CLASS.EXPANDED, true)
  521. .attr('r', r)
  522. }
  523. ChartInternal.prototype.unexpandCircles = function(i) {
  524. var $$ = this,
  525. r = $$.pointR.bind($$)
  526. $$.getCircles(i)
  527. .filter(function() {
  528. return $$.d3.select(this).classed(CLASS.EXPANDED)
  529. })
  530. .classed(CLASS.EXPANDED, false)
  531. .attr('r', r)
  532. }
  533. ChartInternal.prototype.pointR = function(d) {
  534. var $$ = this,
  535. config = $$.config
  536. return $$.isStepType(d)
  537. ? 0
  538. : isFunction(config.point_r)
  539. ? config.point_r(d)
  540. : config.point_r
  541. }
  542. ChartInternal.prototype.pointExpandedR = function(d) {
  543. var $$ = this,
  544. config = $$.config
  545. if (config.point_focus_expand_enabled) {
  546. return isFunction(config.point_focus_expand_r)
  547. ? config.point_focus_expand_r(d)
  548. : config.point_focus_expand_r
  549. ? config.point_focus_expand_r
  550. : $$.pointR(d) * 1.75
  551. } else {
  552. return $$.pointR(d)
  553. }
  554. }
  555. ChartInternal.prototype.pointSelectR = function(d) {
  556. var $$ = this,
  557. config = $$.config
  558. return isFunction(config.point_select_r)
  559. ? config.point_select_r(d)
  560. : config.point_select_r
  561. ? config.point_select_r
  562. : $$.pointR(d) * 4
  563. }
  564. ChartInternal.prototype.isWithinCircle = function(that, r) {
  565. var d3 = this.d3,
  566. mouse = d3.mouse(that),
  567. d3_this = d3.select(that),
  568. cx = +d3_this.attr('cx'),
  569. cy = +d3_this.attr('cy')
  570. return Math.sqrt(Math.pow(cx - mouse[0], 2) + Math.pow(cy - mouse[1], 2)) < r
  571. }
  572. ChartInternal.prototype.isWithinStep = function(that, y) {
  573. return Math.abs(y - this.d3.mouse(that)[1]) < 30
  574. }