line.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import {line as shapeLine} from "d3";
  2. import {create} from "../context.js";
  3. import {curveAuto, maybeCurveAuto} from "../curve.js";
  4. import {Mark} from "../mark.js";
  5. import {applyGroupedMarkers, markers} from "../marker.js";
  6. import {coerceNumbers, indexOf, identity, maybeTuple, maybeZ} from "../options.js";
  7. import {
  8. applyDirectStyles,
  9. applyIndirectStyles,
  10. applyTransform,
  11. applyGroupedChannelStyles,
  12. groupIndex
  13. } from "../style.js";
  14. import {maybeDenseIntervalX, maybeDenseIntervalY} from "../transforms/bin.js";
  15. const defaults = {
  16. ariaLabel: "line",
  17. fill: "none",
  18. stroke: "currentColor",
  19. strokeWidth: 1.5,
  20. strokeLinecap: "round",
  21. strokeLinejoin: "round",
  22. strokeMiterlimit: 1
  23. };
  24. export class Line extends Mark {
  25. constructor(data, options = {}) {
  26. const {x, y, z, curve, tension} = options;
  27. super(
  28. data,
  29. {
  30. x: {value: x, scale: "x"},
  31. y: {value: y, scale: "y"},
  32. z: {value: maybeZ(options), optional: true}
  33. },
  34. options,
  35. defaults
  36. );
  37. this.z = z;
  38. this.curve = maybeCurveAuto(curve, tension);
  39. markers(this, options);
  40. }
  41. filter(index) {
  42. return index;
  43. }
  44. project(channels, values, context) {
  45. // For the auto curve, projection is handled at render.
  46. if (this.curve !== curveAuto) {
  47. super.project(channels, values, context);
  48. }
  49. }
  50. render(index, scales, channels, dimensions, context) {
  51. const {x: X, y: Y} = channels;
  52. const {curve} = this;
  53. return create("svg:g", context)
  54. .call(applyIndirectStyles, this, dimensions, context)
  55. .call(applyTransform, this, scales)
  56. .call((g) =>
  57. g
  58. .selectAll()
  59. .data(groupIndex(index, [X, Y], this, channels))
  60. .enter()
  61. .append("path")
  62. .call(applyDirectStyles, this)
  63. .call(applyGroupedChannelStyles, this, channels)
  64. .call(applyGroupedMarkers, this, channels, context)
  65. .attr(
  66. "d",
  67. curve === curveAuto && context.projection
  68. ? sphereLine(context.path(), X, Y)
  69. : shapeLine()
  70. .curve(curve)
  71. .defined((i) => i >= 0)
  72. .x((i) => X[i])
  73. .y((i) => Y[i])
  74. )
  75. )
  76. .node();
  77. }
  78. }
  79. function sphereLine(path, X, Y) {
  80. X = coerceNumbers(X);
  81. Y = coerceNumbers(Y);
  82. return (I) => {
  83. let line = [];
  84. const lines = [line];
  85. for (const i of I) {
  86. // Check for undefined value; see groupIndex.
  87. if (i === -1) {
  88. line = [];
  89. lines.push(line);
  90. } else {
  91. line.push([X[i], Y[i]]);
  92. }
  93. }
  94. return path({type: "MultiLineString", coordinates: lines});
  95. };
  96. }
  97. export function line(data, {x, y, ...options} = {}) {
  98. [x, y] = maybeTuple(x, y);
  99. return new Line(data, {...options, x, y});
  100. }
  101. export function lineX(data, {x = identity, y = indexOf, ...options} = {}) {
  102. return new Line(data, maybeDenseIntervalY({...options, x, y}));
  103. }
  104. export function lineY(data, {x = indexOf, y = identity, ...options} = {}) {
  105. return new Line(data, maybeDenseIntervalX({...options, x, y}));
  106. }