bar.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import {create} from "../context.js";
  2. import {Mark} from "../mark.js";
  3. import {hasXY, identity, indexOf} from "../options.js";
  4. import {isCollapsed} from "../scales.js";
  5. import {applyAttr, applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform} from "../style.js";
  6. import {maybeIdentityX, maybeIdentityY} from "../transforms/identity.js";
  7. import {maybeIntervalX, maybeIntervalY} from "../transforms/interval.js";
  8. import {maybeStackX, maybeStackY} from "../transforms/stack.js";
  9. import {applyRoundedRect, rectInsets, rectRadii} from "./rect.js";
  10. const barDefaults = {
  11. ariaLabel: "bar"
  12. };
  13. export class AbstractBar extends Mark {
  14. constructor(data, channels, options = {}, defaults = barDefaults) {
  15. super(data, channels, options, defaults);
  16. rectInsets(this, options);
  17. rectRadii(this, options);
  18. }
  19. render(index, scales, channels, dimensions, context) {
  20. const {rx, ry, rx1y1, rx1y2, rx2y1, rx2y2} = this;
  21. const x = this._x(scales, channels, dimensions);
  22. const y = this._y(scales, channels, dimensions);
  23. const w = this._width(scales, channels, dimensions);
  24. const h = this._height(scales, channels, dimensions);
  25. return create("svg:g", context)
  26. .call(applyIndirectStyles, this, dimensions, context)
  27. .call(this._transform, this, scales)
  28. .call((g) =>
  29. g
  30. .selectAll()
  31. .data(index)
  32. .enter()
  33. .call(
  34. rx1y1 || rx1y2 || rx2y1 || rx2y2
  35. ? (g) =>
  36. g
  37. .append("path")
  38. .call(applyDirectStyles, this)
  39. .call(applyRoundedRect, x, y, add(x, w), add(y, h), this)
  40. .call(applyChannelStyles, this, channels)
  41. : (g) =>
  42. g
  43. .append("rect")
  44. .call(applyDirectStyles, this)
  45. .attr("x", x)
  46. .attr("width", w)
  47. .attr("y", y)
  48. .attr("height", h)
  49. .call(applyAttr, "rx", rx)
  50. .call(applyAttr, "ry", ry)
  51. .call(applyChannelStyles, this, channels)
  52. )
  53. )
  54. .node();
  55. }
  56. _x(scales, {x: X}, {marginLeft}) {
  57. const {insetLeft} = this;
  58. return X ? (i) => X[i] + insetLeft : marginLeft + insetLeft;
  59. }
  60. _y(scales, {y: Y}, {marginTop}) {
  61. const {insetTop} = this;
  62. return Y ? (i) => Y[i] + insetTop : marginTop + insetTop;
  63. }
  64. _width({x}, {x: X}, {marginRight, marginLeft, width}) {
  65. const {insetLeft, insetRight} = this;
  66. const bandwidth = X && x ? x.bandwidth() : width - marginRight - marginLeft;
  67. return Math.max(0, bandwidth - insetLeft - insetRight);
  68. }
  69. _height({y}, {y: Y}, {marginTop, marginBottom, height}) {
  70. const {insetTop, insetBottom} = this;
  71. const bandwidth = Y && y ? y.bandwidth() : height - marginTop - marginBottom;
  72. return Math.max(0, bandwidth - insetTop - insetBottom);
  73. }
  74. }
  75. function add(a, b) {
  76. return typeof a === "function" && typeof b === "function"
  77. ? (i) => a(i) + b(i)
  78. : typeof a === "function"
  79. ? (i) => a(i) + b
  80. : typeof b === "function"
  81. ? (i) => a + b(i)
  82. : a + b;
  83. }
  84. export class BarX extends AbstractBar {
  85. constructor(data, options = {}, defaults) {
  86. const {x1, x2, y} = options;
  87. super(
  88. data,
  89. {
  90. x1: {value: x1, scale: "x"},
  91. x2: {value: x2, scale: "x"},
  92. y: {value: y, scale: "y", type: "band", optional: true}
  93. },
  94. options,
  95. defaults
  96. );
  97. }
  98. _transform(selection, mark, {x}) {
  99. selection.call(applyTransform, mark, {x}, 0, 0);
  100. }
  101. _x({x}, {x1: X1, x2: X2}, {marginLeft}) {
  102. const {insetLeft} = this;
  103. return isCollapsed(x) ? marginLeft + insetLeft : (i) => Math.min(X1[i], X2[i]) + insetLeft;
  104. }
  105. _width({x}, {x1: X1, x2: X2}, {marginRight, marginLeft, width}) {
  106. const {insetLeft, insetRight} = this;
  107. return isCollapsed(x)
  108. ? width - marginRight - marginLeft - insetLeft - insetRight
  109. : (i) => Math.max(0, Math.abs(X2[i] - X1[i]) - insetLeft - insetRight);
  110. }
  111. }
  112. export class BarY extends AbstractBar {
  113. constructor(data, options = {}, defaults) {
  114. const {x, y1, y2} = options;
  115. super(
  116. data,
  117. {
  118. y1: {value: y1, scale: "y"},
  119. y2: {value: y2, scale: "y"},
  120. x: {value: x, scale: "x", type: "band", optional: true}
  121. },
  122. options,
  123. defaults
  124. );
  125. }
  126. _transform(selection, mark, {y}) {
  127. selection.call(applyTransform, mark, {y}, 0, 0);
  128. }
  129. _y({y}, {y1: Y1, y2: Y2}, {marginTop}) {
  130. const {insetTop} = this;
  131. return isCollapsed(y) ? marginTop + insetTop : (i) => Math.min(Y1[i], Y2[i]) + insetTop;
  132. }
  133. _height({y}, {y1: Y1, y2: Y2}, {marginTop, marginBottom, height}) {
  134. const {insetTop, insetBottom} = this;
  135. return isCollapsed(y)
  136. ? height - marginTop - marginBottom - insetTop - insetBottom
  137. : (i) => Math.max(0, Math.abs(Y2[i] - Y1[i]) - insetTop - insetBottom);
  138. }
  139. }
  140. export function barX(data, options = {}) {
  141. if (!hasXY(options)) options = {...options, y: indexOf, x2: identity};
  142. return new BarX(data, maybeStackX(maybeIntervalX(maybeIdentityX(options))));
  143. }
  144. export function barY(data, options = {}) {
  145. if (!hasXY(options)) options = {...options, x: indexOf, y2: identity};
  146. return new BarY(data, maybeStackY(maybeIntervalY(maybeIdentityY(options))));
  147. }