legendHighlight.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import { group } from 'd3-array';
  2. import { subObject } from '../utils/helper';
  3. import { mergeState, selectG2Elements, useState, createValueof, createDatumof, } from './utils';
  4. import { markerOf, labelOf, itemsOf, legendsOf, dataOf } from './legendFilter';
  5. export function LegendHighlight() {
  6. return (context, _, emitter) => {
  7. const { container, view, options } = context;
  8. const legends = legendsOf(container);
  9. const elements = selectG2Elements(container);
  10. const channelOf = (legend) => {
  11. return dataOf(legend).scales[0].name;
  12. };
  13. const scaleOf = (channel) => {
  14. const { scale: { [channel]: scale }, } = view;
  15. return scale;
  16. };
  17. const markState = mergeState(options, ['active', 'inactive']);
  18. const valueof = createValueof(elements, createDatumof(view));
  19. const destroys = [];
  20. // Bind events for each legend.
  21. for (const legend of legends) {
  22. const datumOf = (item) => {
  23. const { data } = legend.attributes;
  24. const { __data__: datum } = item;
  25. const { index } = datum;
  26. return data[index].label;
  27. };
  28. const channel = channelOf(legend);
  29. const items = itemsOf(legend);
  30. const scale = scaleOf(channel);
  31. const elementGroup = group(elements, (d) => scale.invert(d.__data__[channel]));
  32. const { state: legendState = {} } = legend.attributes;
  33. const { inactive = {} } = legendState;
  34. const { setState, removeState } = useState(markState, valueof);
  35. // Handle styles of inner item.
  36. const markerStyle = { inactive: subObject(inactive, 'marker') };
  37. const labelStyle = { inactive: subObject(inactive, 'label') };
  38. const { setState: setM, removeState: removeM } = useState(markerStyle);
  39. const { setState: setL, removeState: removeL } = useState(labelStyle);
  40. const updateLegendState = (highlight) => {
  41. for (const item of items) {
  42. const marker = markerOf(item);
  43. const label = labelOf(item);
  44. if (item === highlight || highlight === null) {
  45. removeM(marker, 'inactive');
  46. removeL(label, 'inactive');
  47. }
  48. else {
  49. setM(marker, 'inactive');
  50. setL(label, 'inactive');
  51. }
  52. }
  53. };
  54. const highlightItem = (event, item) => {
  55. // Update UI.
  56. const value = datumOf(item);
  57. const elementSet = new Set(elementGroup.get(value));
  58. for (const e of elements) {
  59. if (elementSet.has(e))
  60. setState(e, 'active');
  61. else
  62. setState(e, 'inactive');
  63. }
  64. updateLegendState(item);
  65. // Emit events.
  66. const { nativeEvent = true } = event;
  67. if (!nativeEvent)
  68. return;
  69. emitter.emit('legend:highlight', Object.assign(Object.assign({}, event), { nativeEvent, data: { channel, value } }));
  70. };
  71. const itemPointerover = new Map();
  72. // Add listener for the legend items.
  73. for (const item of items) {
  74. const pointerover = (event) => {
  75. highlightItem(event, item);
  76. };
  77. item.addEventListener('pointerover', pointerover);
  78. itemPointerover.set(item, pointerover);
  79. }
  80. // Add listener for the legend group.
  81. const pointerleave = (event) => {
  82. for (const e of elements)
  83. removeState(e, 'inactive', 'active');
  84. updateLegendState(null);
  85. // Emit events.
  86. const { nativeEvent = true } = event;
  87. if (!nativeEvent)
  88. return;
  89. emitter.emit('legend:unhighlight', { nativeEvent });
  90. };
  91. const onHighlight = (event) => {
  92. const { nativeEvent, data } = event;
  93. if (nativeEvent)
  94. return;
  95. const { channel: specifiedChannel, value } = data;
  96. if (specifiedChannel !== channel)
  97. return;
  98. const item = items.find((d) => datumOf(d) === value);
  99. if (!item)
  100. return;
  101. highlightItem({ nativeEvent: false }, item);
  102. };
  103. const onUnHighlight = (event) => {
  104. const { nativeEvent } = event;
  105. if (nativeEvent)
  106. return;
  107. pointerleave({ nativeEvent: false });
  108. };
  109. legend.addEventListener('pointerleave', pointerleave);
  110. emitter.on('legend:highlight', onHighlight);
  111. emitter.on('legend:unhighlight', onUnHighlight);
  112. const destroy = () => {
  113. legend.removeEventListener(pointerleave);
  114. emitter.off('legend:highlight', onHighlight);
  115. emitter.off('legend:unhighlight', onUnHighlight);
  116. for (const [item, pointerover] of itemPointerover) {
  117. item.removeEventListener(pointerover);
  118. }
  119. };
  120. destroys.push(destroy);
  121. }
  122. return () => destroys.forEach((d) => d());
  123. };
  124. }
  125. //# sourceMappingURL=legendHighlight.js.map