From d28820858d013326b3c660381e70696e9382166d Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Fri, 6 Nov 2020 19:03:38 +0100 Subject: [PATCH] feat: small multiples for XY charts (alpha) (#793) The commit adds a first alpha version of the SmallMultiple chart configuration as requested in #500 Two main components are used to describe the small multiples: - `` is a generic component that allows the specification of a group by operation and sorting order. The `by` prop request a function with two arguments, the spec and the datum. It's called for each spec and for each data point available in the data array of a spec. It should return a unique value used to group the data points into multiple series. - `` component with two main optional props: `splitVertically` or `splitHorizontally` where you can specify the `id` of a `GroupBy` operator to render vertically (one below the other) or horizontally (one aside the other) the series defined by that group operation. A preliminary style configuration can be used to configure inner and outer padding in percentage for the vertical or horizontal charts. close #500 Co-authored-by: Nick Partridge --- api/charts.api.md | 49 +- ...m-domain-visually-looks-correct-1-snap.png | Bin 20174 -> 24605 bytes ...k-labels-visually-looks-correct-1-snap.png | Bin 23188 -> 23260 bytes ...e-linear-visually-looks-correct-1-snap.png | Bin 15494 -> 15493 bytes ...nal-axis-visually-looks-correct-1-snap.png | Bin 11477 -> 11791 bytes ...id-lines-visually-looks-correct-1-snap.png | Bin 0 -> 93527 bytes ...tal-bars-visually-looks-correct-1-snap.png | Bin 0 -> 20427 bytes ...al-areas-visually-looks-correct-1-snap.png | Bin 0 -> 55977 bytes ...-render-correctly-rotated-ticks-1-snap.png | Bin 52723 -> 52886 bytes ...ries-should-render-tick-padding-1-snap.png | Bin 19326 -> 19500 bytes ...togram-mode-is-false-rotation-0-1-snap.png | Bin 15494 -> 15493 bytes ...gram-mode-is-false-rotation-180-1-snap.png | Bin 15405 -> 15405 bytes ...ogram-mode-is-false-rotation-90-1-snap.png | Bin 15162 -> 15164 bytes ...e-is-false-rotation-negative-90-1-snap.png | Bin 15046 -> 15043 bytes ...togram-mode-is-true-rotation-90-1-snap.png | Bin 14335 -> 14333 bytes ...de-is-true-rotation-negative-90-1-snap.png | Bin 14033 -> 14032 bytes ...e-linear-point-alignment-center-1-snap.png | Bin 12669 -> 12668 bytes ...mode-linear-point-alignment-end-1-snap.png | Bin 12669 -> 12667 bytes ...de-linear-point-alignment-start-1-snap.png | Bin 12612 -> 12611 bytes ...r-corrent-tooltip-in-dark-theme-1-snap.png | Bin 61752 -> 61798 bytes .../line/dimensions.integration.test.ts | 23 +- .../annotations/line/dimensions.test.ts | 729 ++++++++ .../xy_chart/annotations/line/dimensions.ts | 219 ++- .../xy_chart/annotations/line/line.test.tsx | 49 +- .../xy_chart/annotations/line/tooltip.test.ts | 364 ++++ .../xy_chart/annotations/line/tooltip.ts | 29 +- .../xy_chart/annotations/line/types.ts | 22 +- .../annotations/rect/dimensions.test.ts | 105 ++ .../xy_chart/annotations/rect/dimensions.ts | 27 +- .../xy_chart/annotations/rect/tooltip.test.ts | 49 + .../xy_chart/annotations/rect/tooltip.ts | 51 +- .../xy_chart/annotations/rect/types.ts | 3 + .../xy_chart/annotations/tooltip.ts | 7 +- .../xy_chart/annotations/utils.test.ts | 1278 +------------ src/chart_types/xy_chart/annotations/utils.ts | 39 +- src/chart_types/xy_chart/axes/axes_sizes.ts | 112 ++ .../xy_chart/crosshair/crosshair_utils.ts | 42 +- .../xy_chart/domains/x_domain.test.ts | 34 +- src/chart_types/xy_chart/domains/x_domain.ts | 1 + .../xy_chart/domains/y_domain.test.ts | 10 +- src/chart_types/xy_chart/domains/y_domain.ts | 198 +- .../renderer/canvas/annotations/index.ts | 12 +- .../renderer/canvas/annotations/lines.ts | 27 +- .../renderer/canvas/annotations/rect.ts | 15 +- .../xy_chart/renderer/canvas/areas.ts | 97 +- .../xy_chart/renderer/canvas/axes/index.ts | 89 +- .../xy_chart/renderer/canvas/axes/line.ts | 14 +- .../xy_chart/renderer/canvas/axes/tick.ts | 6 +- .../renderer/canvas/axes/tick_label.ts | 6 +- .../xy_chart/renderer/canvas/axes/title.ts | 14 +- .../xy_chart/renderer/canvas/bars.ts | 57 +- .../xy_chart/renderer/canvas/bubbles.ts | 44 +- .../xy_chart/renderer/canvas/grids.ts | 51 +- .../xy_chart/renderer/canvas/lines.ts | 37 +- .../xy_chart/renderer/canvas/panels/panels.ts | 43 + .../xy_chart/renderer/canvas/points.ts | 29 +- .../renderer/canvas/primitives/line.ts | 16 +- .../renderer/canvas/primitives/path.ts | 17 +- .../xy_chart/renderer/canvas/renderers.ts | 103 +- .../renderer/canvas/utils/panel_transform.ts | 55 + .../xy_chart/renderer/canvas/values/bar.ts | 58 +- .../xy_chart/renderer/canvas/xy_chart.tsx | 51 +- .../renderer/dom/annotations/annotations.tsx | 7 +- .../xy_chart/renderer/dom/highlighter.tsx | 22 +- .../rendering/rendering.areas.test.ts | 1541 +++++++--------- .../rendering/rendering.bands.test.ts | 608 +++---- .../xy_chart/rendering/rendering.bars.test.ts | 1613 +++++++++-------- .../rendering/rendering.bubble.test.ts | 1566 ++++++---------- .../rendering/rendering.lines.test.ts | 1542 ++++++---------- .../xy_chart/rendering/rendering.test.ts | 27 +- .../xy_chart/rendering/rendering.ts | 62 +- .../state/chart_state.interactions.test.ts | 53 +- .../xy_chart/state/chart_state.test.ts | 16 +- .../state/chart_state.timescales.test.ts | 6 +- .../state/selectors/compute_annotations.ts | 4 + ...le_ticks.ts => compute_axes_geometries.ts} | 24 +- .../state/selectors/compute_grid_lines.ts | 35 + .../state/selectors/compute_panels.ts | 38 + .../selectors/compute_per_panel_axes_geoms.ts | 73 + .../state/selectors/compute_series_domains.ts | 16 +- .../selectors/compute_series_geometries.ts | 13 +- .../compute_small_multiple_scales.ts | 73 + .../state/selectors/count_bars_in_cluster.ts | 44 +- .../state/selectors/get_axis_styles.ts | 12 +- .../state/selectors/get_cursor_band.ts | 40 +- .../state/selectors/get_debug_state.ts | 218 ++- .../selectors/get_elements_at_cursor_pos.ts | 21 +- .../state/selectors/get_grid_lines.ts | 35 + ...get_oriented_projected_pointer_position.ts | 26 +- .../get_projected_pointer_position.ts | 66 +- .../xy_chart/state/selectors/get_specs.ts | 29 +- .../state/selectors/get_tooltip_position.ts | 30 +- .../get_tooltip_values_highlighted_geoms.ts | 3 +- .../state/selectors/on_pointer_move_caller.ts | 8 +- .../utils/__snapshots__/utils.test.ts.snap | 1143 +++++++----- .../xy_chart/state/utils/common.ts | 4 +- src/chart_types/xy_chart/state/utils/types.ts | 27 +- .../xy_chart/state/utils/utils.test.ts | 722 ++------ src/chart_types/xy_chart/state/utils/utils.ts | 499 +++-- .../xy_chart/tooltip/tooltip.test.ts | 27 +- .../utils/__snapshots__/series.test.ts.snap | 1047 ++++++++--- .../xy_chart/utils/axis_utils.test.ts | 232 ++- src/chart_types/xy_chart/utils/axis_utils.ts | 248 +-- .../xy_chart/utils/dimensions.test.ts | 1 + src/chart_types/xy_chart/utils/dimensions.ts | 75 +- src/chart_types/xy_chart/utils/fill_series.ts | 102 +- .../xy_chart/utils/fit_function_utils.ts | 17 +- .../xy_chart/utils/grid_lines.test.ts | 54 + src/chart_types/xy_chart/utils/grid_lines.ts | 150 ++ .../xy_chart/utils/group_data_series.ts | 47 + .../xy_chart/utils/indexed_geometry_map.ts | 16 +- .../xy_chart/utils/interactions.test.ts | 27 +- .../xy_chart/utils/interactions.ts | 6 +- .../utils/nonstacked_series_utils.test.ts | 72 +- src/chart_types/xy_chart/utils/panel.ts | 25 + src/chart_types/xy_chart/utils/panel_utils.ts | 59 + src/chart_types/xy_chart/utils/scales.test.ts | 138 +- src/chart_types/xy_chart/utils/scales.ts | 31 - src/chart_types/xy_chart/utils/series.test.ts | 193 +- src/chart_types/xy_chart/utils/series.ts | 336 ++-- src/chart_types/xy_chart/utils/specs.ts | 1 + .../stacked_percent_series_utils.test.ts | 60 +- .../utils/stacked_series_utils.test.ts | 138 +- .../__snapshots__/chart.test.tsx.snap | 2 +- src/mocks/annotations/annotations.ts | 88 + src/mocks/geometries.ts | 31 +- src/mocks/series/series.ts | 8 +- src/mocks/series/series_identifiers.ts | 18 +- src/mocks/specs/specs.ts | 7 +- src/renderers/canvas/index.ts | 4 +- src/scales/scale_band.ts | 18 +- src/specs/constants.ts | 2 + src/specs/group_by.ts | 50 + src/specs/index.ts | 3 + src/specs/small_multiples.ts | 54 + src/specs/specs_parser.tsx | 1 + src/utils/commons.ts | 7 +- src/utils/dimensions.ts | 5 + src/utils/geometry.ts | 14 + stories/axes/8_custom_domain.tsx | 1 - stories/small_multiples/1_grid.tsx | 232 +++ stories/small_multiples/2_vertical_areas.tsx | 105 ++ stories/small_multiples/3_grid_lines.tsx | 163 ++ stories/small_multiples/4_horizontal_bars.tsx | 159 ++ .../small_multiples.stories.tsx | 31 + yarn.lock | 6 +- 146 files changed, 9827 insertions(+), 8831 deletions(-) create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-small-multiples-alpha-grid-lines-visually-looks-correct-1-snap.png create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-small-multiples-alpha-horizontal-bars-visually-looks-correct-1-snap.png create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-small-multiples-alpha-vertical-areas-visually-looks-correct-1-snap.png create mode 100644 src/chart_types/xy_chart/annotations/line/dimensions.test.ts create mode 100644 src/chart_types/xy_chart/annotations/line/tooltip.test.ts create mode 100644 src/chart_types/xy_chart/annotations/rect/dimensions.test.ts create mode 100644 src/chart_types/xy_chart/annotations/rect/tooltip.test.ts create mode 100644 src/chart_types/xy_chart/axes/axes_sizes.ts create mode 100644 src/chart_types/xy_chart/renderer/canvas/panels/panels.ts create mode 100644 src/chart_types/xy_chart/renderer/canvas/utils/panel_transform.ts rename src/chart_types/xy_chart/state/selectors/{compute_axis_visible_ticks.ts => compute_axes_geometries.ts} (82%) create mode 100644 src/chart_types/xy_chart/state/selectors/compute_grid_lines.ts create mode 100644 src/chart_types/xy_chart/state/selectors/compute_panels.ts create mode 100644 src/chart_types/xy_chart/state/selectors/compute_per_panel_axes_geoms.ts create mode 100644 src/chart_types/xy_chart/state/selectors/compute_small_multiple_scales.ts create mode 100644 src/chart_types/xy_chart/state/selectors/get_grid_lines.ts create mode 100644 src/chart_types/xy_chart/utils/grid_lines.test.ts create mode 100644 src/chart_types/xy_chart/utils/grid_lines.ts create mode 100644 src/chart_types/xy_chart/utils/group_data_series.ts create mode 100644 src/chart_types/xy_chart/utils/panel.ts create mode 100644 src/chart_types/xy_chart/utils/panel_utils.ts create mode 100644 src/mocks/annotations/annotations.ts create mode 100644 src/specs/group_by.ts create mode 100644 src/specs/small_multiples.ts create mode 100644 stories/small_multiples/1_grid.tsx create mode 100644 stories/small_multiples/2_vertical_areas.tsx create mode 100644 stories/small_multiples/3_grid_lines.tsx create mode 100644 stories/small_multiples/4_horizontal_bars.tsx create mode 100644 stories/small_multiples/small_multiples.stories.tsx diff --git a/api/charts.api.md b/api/charts.api.md index 6eaa12c754..d9ec00faf2 100644 --- a/api/charts.api.md +++ b/api/charts.api.md @@ -773,6 +773,28 @@ export interface GroupBrushExtent { groupId: GroupId; } +// @alpha (undocumented) +export const GroupBy: React.FunctionComponent; + +// @alpha (undocumented) +export type GroupByAccessor = (spec: Spec, datum: any) => string | number; + +// @alpha (undocumented) +export type GroupByProps = Pick; + +// Warning: (ae-forgotten-export) The symbol "Predicate" needs to be exported by the entry point index.d.ts +// +// @alpha (undocumented) +export type GroupBySort = Predicate; + +// @alpha (undocumented) +export interface GroupBySpec extends Spec { + // (undocumented) + by: GroupByAccessor; + // (undocumented) + sort: GroupBySort; +} + // @public (undocumented) export type GroupId = string; @@ -935,8 +957,6 @@ export interface HeatmapSpec extends Spec { xAccessor: Accessor | AccessorFn; // (undocumented) xScaleType: SeriesScales['xScaleType']; - // Warning: (ae-forgotten-export) The symbol "Predicate" needs to be exported by the entry point index.d.ts - // // (undocumented) xSortPredicate: Predicate; // (undocumented) @@ -1635,6 +1655,25 @@ export interface SimplePadding { outer: number; } +// @alpha (undocumented) +export const SmallMultiples: React.FunctionComponent; + +// @alpha (undocumented) +export type SmallMultiplesProps = Partial>; + +// @alpha (undocumented) +export interface SmallMultiplesSpec extends Spec { + // (undocumented) + splitHorizontally?: string; + // (undocumented) + splitVertically?: string; + // (undocumented) + style?: { + verticalPanelPadding?: [number, number]; + horizontalPanelPadding?: [number, number]; + }; +} + // Warning: (ae-missing-release-tag) "Spec" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1655,6 +1694,8 @@ export const SpecTypes: Readonly<{ Axis: "axis"; Annotation: "annotation"; Settings: "settings"; + IndexOrder: "index_order"; + SmallMultiples: "small_multiples"; }>; // @public (undocumented) @@ -1871,6 +1912,10 @@ export interface XYChartSeriesIdentifier extends SeriesIdentifier { // (undocumented) seriesKeys: (string | number)[]; // (undocumented) + smHorizontalAccessorValue?: string | number; + // (undocumented) + smVerticalAccessorValue?: string | number; + // (undocumented) splitAccessors: Map; // (undocumented) yAccessor: string | number; diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-custom-domain-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-custom-domain-visually-looks-correct-1-snap.png index c32ca6545a22c8d0b0bf540660369f4c560ee95b..59944481184812d31575f242f5bbe51e92fd4e36 100644 GIT binary patch literal 24605 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 literal 20174 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�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ⅅw&*xEw%B`bNh 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 DR3Cw7 diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-test-histogram-mode-linear-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-test-histogram-mode-linear-visually-looks-correct-1-snap.png index 9ad6c008b682dc15e2fbdfc0b9509eb0ba1cbea3..33411046ca89b0c13e7441374174d6f96e12ed52 100644 GIT binary patch literal 15493 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 D^TN60 literal 15494 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 E0W&F|5C8xG diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-rotations-with-ordinal-axis-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-rotations-with-ordinal-axis-visually-looks-correct-1-snap.png index e06b1fd9647f7ca6e25ac622f664896e82cde632..085f597431a3dec1c85f6300fcb5a7a098ab800b 100644 GIT binary patch literal 11791 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 PIvwP?&b3mNTMzyNvnLSB literal 11477 zcmdUVc|6qp*Y{LcyCqji2+2s6P_l=LXl&VKCuGaMkF^C|3N_ZOm9dO{mz{)=ZNy~H zZVV<2lNrnN`KI6XyMNDfKlkr`{=1*o3uC^Y{d~@OpL5>l`OMo&D~B03Vv^9M}*9E=LQiYxOS%j@#X%PVw^aE2;ZOcYM+ zqL1m_vDW0&@PeQEQXl;+uXKC&tsE zgJ3gxR_FAT%=zhZ4}mYR-qQ#v@L?_j2YZ7BcfCSGkHzFsbl~F~&41PSvNPvop>dU# zudnYdw*K+)f(-(WUtC;2BqW6I^5uoB1uz4Qa6b+`KRb&Nt`p9-)XO)^sO|G*VHYuG zkIG8B2rZRoZGd9WYvvAPWMt3ykRajd6IK!_d;Bt@FegXL)wTE{7uO+KS7qf6Nt}a&gD#bFm-7YHws^M=w4{G?7s{H= zJnF>`HlLd(x1G~Q_;c{+fYcJ_Te!@%Yg&>9l8F*t4{q}0XJcN8^kK?k%1Vjay6@1R z;9CQJsKE92Ni}lgwce#t)l%0l`hJq^p$8kI(F>Ie*4ivEshRP^Wo^4Y)-5=Mzpv(Q(aK9)@w0uBW2Dqq9OI|F!kWpmW~e| zS?wG5IxUcRO@*E)q@_NB=6))bdwOWLYw3Qbs@VQWR$WhJ|k!OdA=I zHqygLbxSwuWCU&nBDh9INDI3u;Xk%0lcgMRcPuqYWGv$=y|Z~e4>(gs>W8Vd(gGQm zvDidkXLAHa<*#<6J&}$XRevL}BrrY>lhmm)6aQ{xTsr4T+-ZB45WAM2?G$(AP+5bm zR5)JYeu94}sV%6Uy8#`GNuLp+srxXkR<$k5_B?VKw}>=9^a%(l_&}NC$8V(t%ROI? z{k*Yf;A*^@DoDHTfiJb@rci%q%inezR=2lzPd5&1Q1tLz4DHZbe#tf%kUU8yw619% zui2^2Kr^LWwCjqUEI0}Q^TsTs6x{2cxJRzn#awSSzdPH~HO9i#~ zaTIw$MBneh)XY^HPl@v#_Rh=W;)4Xu4|`Ab;v8~2b?3bZ4wgKavB}=4MCBcQJJ$x? zS`1|Djb0Cv)93vkAnY3FF>YbrLB@DG-1BaA1hz-S{mlF_HYdmlSN;A9rDK&#N>@a# z<>zPO`N_bvU25F}nFfd4z`BOJglN=;{cWvA*FRpsO;DAi=^b2*IXQV~4mL3V5hv5V z4g;Lt_C`x^&pNeCHN_si_DjG4Y0qSct~4{&^ga@1n#(hbM+vbV3YM+w^7e>H0Uvm2SZ_T$)&%NHNfXCb^D zgn>4DKGB+D!I_@*M*@Crnl+PbN(W#I(&MfkE+VsRO7bbu`~~i8E-4`Ji+{%E^Wyk+M3W<*rV1GWy)lFSnRp9Y_$~QZJdsVWab*s(hH}O%90vCNBvX^`)iy`Zg8xO&VD*R*;*=QF&rIgcv)0a9%BCsVr_Pc zlGh^Ycs%L;z2}JD@C^PlZ0z|{9S`z{Yn4wv6cApf>`OD{+eJh?JZw^=&MR z{mOR-iS4?mr)28(H7imz2bipPLf{@&uJpTz&8Vyh@#$*#VapRb!kKTW<(%?&X76OS zG$wn1Zr}d0Mj-ucYFLJ;vWms&>CK;P!BZHuNC45pmXbSH(a5p8ot=DD@{%GrY4EGw zzs&hMQKT8PRjwc{G(K@w2JiKIE(Fo?9yFTf@-MYL#^#Py`=?g_Hx&Or?*gO}{~H&IEVTQ^hrcxaf``Y&gn!rN3qqXn??EplA-gJK`d9N>P!Rq{7lB-K18Gr(MaJIa zCUr0^8^=$qy;z9j4{LsRa$WH2v!&x5Up`;wKKoBY;Ns$nWqC79jYNgkggGoRS~AYj zXuCUd?nY;-C7%6J z%pNwMOqj$l?CY^3O(J|J0O5)Vj|kVbRIzMqE6dpc?eC5phCThl&&Z9mBGTnH-{csJ zle-h2Wo^KABslQ;WfW;R)38OcmG6dtak*MKUoOA4lb%y!TiezY<*F3yoWK3BVR?D= zmqp=TC7jK?0oy?r2)jhVz?@dTot4>%cEUQ}C}V*`rU{pG0&cMgY&tmbDrn#S;y-IM z=iMM?m7QJtxolm82f{nA2L@26Msgaf;ben?mgFdaDks-Z4h#(p%}*F8qfJ%|`#{cb*XK z#P_!rFu6l$Y%cT*lwnElpinlrw-4z2zv*(7XH4hsL4x~AVfr@;L+5;#e!A)3b+J^h z#(={csaJevn;TqI%k?ZHWuI3MD-kL2?6Ssvo?I~aUYL`MwG8rA)nw2_p|5haEl}>@ zI`J-s4A9AY3&C()L#^p)w@a7BZs_Uv)Ec5l_I`H_sbUq^`FLd!>sj5z z@d^i`pjwrAIB&7=-*=Ik5}SX#{d7!xGOjLPD&3I1Fsn@rk#iy!yIZs~HbTqH$0x+n z>jQ-DQ@nS-a*w{%pZ|tQ|}T=2x|58PB3z+;zkT% zhQeQj1EX?poR`Dj@LgfcQO_Rh7i{S7`GpGiy8(D^1Qg~1@RW7k;<56#^|)u^o+2fa zqO5XPd%9h*Md8!uSr1HYy>Y~qi15hgbU#@H&4_846NLD4XCWwOExL{%Zv_k&xj%i+ z^?sv&KrR-p#x7&>y+wA&tO1?A`#%IG zu;Du}OITcAl}+m@yPpsZr7_aQ0*I&=5&bo^#Lcar-@e^HoesnfTWT(W2l#jU>;rHq zB;K$Ar~$UXyP%BR`3qU@S;osHDQ`Vsjhx(IiaC|^Hp4xh?+fD~$JI?~=PD&pN6~BV zY4^3%?G_Jy!ha#j-od%wxq)mHfafvOy5krE$NLPpj;&&(Rq0QGsn6Lzaq^bb3slwS zQ61=8OV7#9@6CH+(82sBU4J65Ty6@wYIE~*GJBkGFk}95et1*_u6WH7g}!I9!Fwg4 z{w9!k=20l#5(K_zx3AgWF)N zlonEf8cw!bDXR*Pd^uR~wC4Jy0M(#RzowGBrH^&4*%ebul2BV_Tdww5Ygwc2RC^BP zv%Hpg|9e|EK}d8^8OzGTGV{n&8I?oGjeV2Ii~lO2h+v7n#R!?Fu*yvYCB=Ija}{q{ zR^pNoH(pH2qt@X1$&ez#V$z}~I94HO6MhR|=oT66 zJY0{Eqwe&;5e$sKa7njKL_xOfl0&M`{oiipqce<>+DG~kKkNin`njTdcXv}89mKY;~KwTGf zNKU59%2ZFVN4h}r(KrL-Q@i$|;Ej6(d+quy1UCF$Uauo=A~WXGnf|Ac0$-RUlefcO z{V}#OwTg_1i^+8H{y@W=lT)ars`GMubmyVQJnyhJI)>pf_a;%dK1)1&T*JXH_afIt zPkf9@YBVV&dR9Hdd?c|wIa(a%N;gJLrtMUisNok_QUp83arYANg*hEDAMaiP|Dp4 z2a3Zj7nc^l@)T~PSAymO5!kGPZ5@qygtDrZy=u<(;!Y1I?^O@2Wr?CyJKlxv^9JaA3I^gX zzJ;Soa(tdXi^1FrjI@l%*|b92_7;^RGf2=QnDI#g+9;#G$~0(rn`g`gPa7T;8L$3_ zYhLbdGJy*f{U(~uXkXy2umiAj3V@**A05n_*qbIB-momj z{)v4eE87?Sjb8J>AsaLKh``;RWoQr!Gn)!Sn((^5KFgI>d#L*Gd< zbr(zUft`3j&K1B;{FvLDg0!GIr#Z<+JZ({q&R-&13!|w=q z$L$Ij*=P@jnWPH()~2$LM8>fV)K3N6O35m((~Z3n+hn@?bb7>)>c6D4A7FU>>4FOE zi6&pZc2NsO!dAqWQGYlgP-cOQ@n?_jJX8$X3bA*xqgza1Z@b60v|bDeDdli7nxjx^ ze8<(&J)I?q)h|ydrTlvK$u@8n=VEk~P)i=qIlcg%@4(KCL!&1Xv2m1yK9|NO>$wSTCTkE2t zb~#g1Qy!glmA2z$zJ~SYzR*tofnGkHRLvUuoGLWL1Up^s)2wIt)zwY(*{?3|1!_s)12%i%7EO+J+ zu~$Gq8FOe}Zqe9@ZK!h{t;}oP z&>C)Y7e#ko`Yn@b(5*LiZ+^M6;9E~5@tG&8QGv}LS#|m~(*4*Qlu)G4X6%}3@)5H2 z->3ifdivNxfIa@QX}h(nJvn*#I`&#QaYFYCavz!vK#2M~nV?+%?!RjNqWot`%UGe& zh|$34aNf7<`~3%@03|f%?TAVDjEdYA>g*2rqKNuopkqzX!4|G@|H?4TQ)SC*lAWPL z2KpSj#{=Qo?E^e`S~c37fU zfZBb!M|td~G{0#h5NEN^S-`);Wh6Nb(zO&fYy?0z*e4Gr@Hb&Hy|fRnNe@y@EU&2_jwqAS91Zw?t!)69*)G;536bexaqVV3K)fR44( z4e}yUN!_aBRpFaT4x8VGkoMEs9g#hHIzC_RsCuje9m?I@roM%O1u9Mv9iyd z&`#h|O-IQtxO+<)yGg3kx7$h7;JDjgab`733O|O_>(HZK5;}e$7XMh3{_>@ZiYJAP zZ9So5{}le6zC26#BmaK&?Y_xAz5;>4oUYt5^VTQ9QAA;>?y5lKQz+RrPr3CPUDG`3 z)_Dcdq28rutdpE*lypdTeELVSdmU!+*>K(Rn)LR5_%iz_35^rQVfyly-A;Ir-?MJWlS#qKWd#F^|i|29<+aSDAFdWD-_USCz;&xTwAO9T+! zQps-Bh%&In?vo(b?y&F#Gr(ZTlIn(Zz2nH`TvImxUYvO z%q!4Q^-#S@tthKl*osfn2Z8X3Rz}-N?vt~0?w!kCWcL{8OvfN5N&qoI>DE*Gl6tPU zKE))Zqo+gP@OEONVWN(yVL|JlqFRFsQcmeO=udM4P$pkqs?O5&#F{Mk~YdtUv+M{O>o)3H_Y2K`PIkNH70&XhxgReMughN&`5i^2d3 zPKjMcd8AEbr4{UAZkR@apUwCc9BI3er{;tI)2V)lI)l@q;A0jNqycNQJ8k$wU z{U+e~sf);7HBfXJpT%R_3!?u+zTU5e)fv(oB2P#6tG)=?Cs$6ZOnf;8Mm6G(-1&8~ zN09)frXYe8Aj~_IVEdaDt;I0$A4w2jC7|%HS_35f{&yuQ)??rMZr=XaF8?FgC5R~6 z+Dz)W+tu4%5k36DG2L*)FjiRL79bU0bqo+;f$T|rUDMxnNUlB0=W;qdAL05HNutn*U z3W|m~+(jwj?8$wGgC|Z_Vi=E);gp}Wm_(sJWJ`#ai>0_ulvH1nDVjR~(q!Aokdr+h zpd{8s7Rrf(&<}R{a72~=j66N|Q%bhkyW7*I(FV$Wm?1}^5=|oWFMwbiY)W6-AsSGp z>LWe>Cza}%c;2R zgO0n0WjZ6!3xT*V@nHrx+V0jQMl4R=I!vcuC%vVgDHmO>!1_Y0W1h^5Wy1iLSUaij zq3aAd?ajTS8wd^%kXM?z66{87=DL=4KbWgNKGAx{W1qURy$L0|H(?NS1nj!ITb)hL zwU*fC?U{J)mzUs(C2cfl4*w_%j64O2Dl|g|A4;<| zKy@}bs!VVDG5a{ASU&eZDAI9Y7DMg(r}n+il9k2{2@s;9Ei{{}&Dz>N<>uGr2MG?2 z{Co==j8`$-EsQtd4;r$pnu)F?`*c;;;izjn#2(L@fPnk-PGgJI`DQ{V_n2L2AwA(K zb-sTs`LD4(VszdfrHFl$0=~(ZhIs__ZaxSHiT~iBUTkbk`_BdO1p)g-hnNMm8}qW| z8|66>*|BoL&Q?Z7NydTk_5}MHvr43quBI-7#*Kmx<8obeW?|KdHKaZ%q^_=R6t1bj z(&gj^)p-!9t*tHe`eS!uVxo?&uJzE4dxa;Bliq~fsM&nwtNK0S{EP4hyeQyOoe zzzLjJw+~pggGu-V1@)|~tQvV%@*S=)a?8$SwvsZ(bqf`Gsz%-fo4ed=dNisn%u3cV z=gZ-nQFYzSf2GV_7j|p)dp5l5jg*pmW;Z_2M90+1;`%T11|g1jl`$vxuzFViKZ_kg zmyaz)yaXMB-(`}EdB8ugBWM0EuP{Wuy~=a;;dJVgp$}Q+Vk^8f*M9+W$l?pvX!M8^+QjpLO72RM$&>c7vQ`Qnx7m-7yD>!>naw#y?<@)-&nLJAps?c$yGtL@5vAtW91b25~> zu5@j`y{$3ckhRG^mU|K_sDYL)@r&>~5zy4bo3N}()-mvT17sJIlH4T|5~zfV3|{6P zN*CX(<1FmSP56AyM2>*WmH+5#zCPZ?hY@_;M(A4`>a_bPe|I~uL2P*#eaQN#)#u)s zzGzXvxeQoXoRT*5)FD(9YFqiBv8Vg<>5j@DLr5KrOG(T}7(~Mn(veHONz{q2hpU+G zpJzN$bJrW~WZ>}v)bzd%FFQchLIbuxj})6Id1KzcyIpDGn*@j)Lutc-(Sb*v_s^lh zvU<)2`S-eZVo+h+ghJ=tfyGzC&t9Rq%)1=BkWQWM_QXf}@mVU(@58`M-G`W7%5JbbP>dQ}Qt72+c`NW%f7$1m8LBx65G_ zM&}trPoO;hSaKfO%?x1@)X{T`Q+80;7_-NZf8%JM4H4)dgPDsy;=Hm z$2@{AnmeXAvam}VqsoYN0cVw-uD7gM)_@GHlg|EA5yLealox628>tvc9jlz(@a0DE zNUIy3XE;&{TyeQ)1>TB^p)R;=S-jx-r~$&B14-h(si^_W2HdTe_@pilFS@v-{-D%S zx+oK$iDUxB0N2T1IKXLfs^$XWpsuJMUY`)ioz;)|cr}D2Npf(pNcb+`OIk$E5{7W8 zx}b16D(Fo-df?L4?LfJpb1^!anw-wD`gXi_dSMTw8Fd-gDz|#1hQB<&{epEgNM7W2 zeOqiyf)#=Wwp3VMk?1;ji4(CGAI)i}9fc z{&PMc*OD|!Ui&L88rGdxfL8Bj2uKFC_`BFWZazL`hE)b-HimC|!3UBX&PQse1Z%Hl z+FGsMbsQ;Glcrz!nx4xs(CjU9zE|@7#q{bu>SP+fMS(Cv>toTgTN~?h?L=8S&ttG7 zkE=taa^)qQ!JAZ#2L4OfD7(HdcaWp%wgs{^KTE@3KL09Bo#&9WM#{(k#&z(=2Iqp zO5~aGknmi`CY6)cGHkYuQ++3yAt5fZ5N6z}V`5V?lHy16-bC9hf@K3JHED6%SP}A# z^qEgqW2BTMkG!db`k)bVxjE%v*Y46IAXb^Z{`uJCggm7*KOLdk-6t%GEN4zLr}yF0 zJ&8;!x>H4;0(T3*TR@FKZ(Pngm>K1A$ZT-QP)5^H=&k-a71Xfw$VLXW9<4Sa4plw+ zdDQd)^F0$xZE? z$gQ4+wc5a+GxWNq+6BH$UD5D-f7gt_ZBmrqCv82GOr{ARO~89#-dEiA-dPirsi0hq z=w!*NKJ7^vA_ip+w+@R03MzF*@d@$Y6E_V=pHolD4QQc~8tC^5b69ggy#XT>xcvg) z5-#B0L>3S32F8-Ht?b8Ch@o&mlifVhZ(@Y9JUqcC#-}GPRAr!V>=t5ZXauOXhuuu< zu$v(B>3-Pum~GhAJPh7G>tXj`Qvrb;95d22G|2({waj)z*sO~LX>7t@pS8lNcfIgN z;ojEFo~`q$DVu;~Ht*@og-uo`qU^lf_-+`WADNBu_B zaqRPF4As-E(%nBj?TEJVaF8F_>MUM1Np!_P?5*{=(Bo#FuwVs{1KN0!^SvD=TTYg* ze^(D!Z8Q4{QR$g8YMQ=|Rd8Mr(bAfr01t{SLd)9Gx);Y=$V+sM)Z|SEnhKy**MQu# zg_??vkr2>aPFWrs887^%q@<{VHOL);?0{o%?-rvd<66)HzP~lDbtTHVmZAt5F3O`T z+2IT;qhG6g*9Y@Z!6f7xQ@?r90K%j*0)<6kNiPa_Z!@R z;BwgHEAV!PohJqxjpaswn#tOhx|)=vp_dLSCedTjLLPerQ?CgkL`Wg$+64s^LE;aWUnGH0M)~B$%4|W z+4d^a*!=eN#9nsEVu?s<&#VW2ul{Gy@Q)uyWZZCE)mCfVP^s=@VMDKj04%r>yekx3 zdY?LMxO}5X8^jHtK?Zc~hL$9k`xyrIcO(WpUm-7!DUfuSn^pze>D(X?-=D!9FZdeH z&2vcwC019CU{6yLxzq&}|CH754$3a~_Y+bFN2ue2P(@*Eo8SS;SNJBm8`)!F-nk3r zui^NKhucY^Ei@;$)_)-D#yGq^yljcD>!jAkU$awh07GKqlSJPUVZuKdJ?XJ0LMDv! ztPIGT1!YqU4--e~ZED#4cCV_jOB(=hj;XFUM1g|lDYraWur|m=8V!~tisXa*mYs95 zF}*Xz(Vi3WL~Z{^wHHSjD6_agjBo!OrYuKigzTi>^x@#nc6S=TjZP#SAO1$)R(bqP zimmLshDX~kyx4}h@j5E)3uS#$E~H?HpoJuX>Wo`(5D!q}ZPqSxr7HRcnC`SO?!7Av zq7X9u*}>&grrH{^7e3qWP=p!=YchyAF8&r|%1U!h`PyE-&I9B3^=p3>E(}-U>=&P5 zhCOZL_3A&Ym92BP@Bkymhja-otj=@jgUUcbQcy}F-Cgx-d3g#XrVHs)(=8oc zUFA|Q*AFfWa%k!3SOMn(gFQ90u&^LXryLK#0>1^)PIDO)iGAV!Jy`u6=5FmJ##_|* Ty*dchf?$X{>Iwz7&7b@);jczf diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-small-multiples-alpha-grid-lines-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-small-multiples-alpha-grid-lines-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..56d31cf0a8a38c60877cb76943d0349b298df6fa GIT binary patch literal 93527 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 ZXOeaJujZn<#v$Mjp{S-%B4_^cKLEWvP5A%- literal 0 HcmV?d00001 diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-small-multiples-alpha-horizontal-bars-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-small-multiples-alpha-horizontal-bars-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..b24d2d81abd36829222c7851353ee54967e20c23 GIT binary patch literal 20427 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

L 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 literal 0 HcmV?d00001 diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-small-multiples-alpha-vertical-areas-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-small-multiples-alpha-vertical-areas-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..5bc3abb744c9e5767a3ed47985fd934fe187c905 GIT binary patch literal 55977 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 z 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 OH2Ie*(q)pyf&T~I)Nc*| literal 52723 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 F{{whMsmcHV diff --git a/integration/tests/__image_snapshots__/axis-stories-test-ts-axis-stories-should-render-tick-padding-1-snap.png b/integration/tests/__image_snapshots__/axis-stories-test-ts-axis-stories-should-render-tick-padding-1-snap.png index ccc0872acbb7d96de274a781a78386bb48154482..ff2ddff8c96a0be58eaa2c7ea7c87025eb73d078 100644 GIT binary patch literal 19500 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 L)z4*}Q$iB}4X0sq literal 19326 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 K=d#Wzp$Pyu|3NAM diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-0-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-0-1-snap.png index 9ad6c008b682dc15e2fbdfc0b9509eb0ba1cbea3..33411046ca89b0c13e7441374174d6f96e12ed52 100644 GIT binary patch literal 15493 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 D^TN60 literal 15494 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 E0W&F|5C8xG diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-180-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-180-1-snap.png index 44089e684fdf66a14dee9299271b8d8fcfa51b7c..13220478d66f20a24ff2257646922416e0e2a92f 100644 GIT binary patch delta 5314 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 literal 15162 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 diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-90-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-90-1-snap.png index 418d9ce35f86b34cbaaae1997d3ade0ee2be7b2c..d85f5deace5de64b7b34c96288c52a016fb1206a 100644 GIT binary patch literal 14333 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�-7FPoSqPv;HCEn4>GpkvudYI*XvbT^AYSE`5eg`ff^RXQ!I-UikI-6|+vw1nJbSYy-#7F;@hHgu2*Wt5Umj zjRmEpdvW5(1P4c3OiNsc^NNbZA3u2_z{{(o9w**0RPC$Y^q*54W?(qM!C^W-*V}8z za^l1(wZGuP7;C@smNug(OvX&2hHXYk$kV4~FQkzfd2FlFm6eqRWo2u>5C8uCTLq2I zTG|rSoiY`U-dpoq$TxGfUeWbJ?27G6MYk9WS|+#qWH0 zsxmAqD!NcLAf2dje?ma}X}!l4?KXj`vIou8HAYvdq8l&dTbljexNhoLYCV1Pm5Fl2 ziHDC*jzecEHf)dV$iW4b2Q@xP`vdzM@1gUJOY=qwpDHuvPFTTYk6^Bgw^pdum zS|RNeofh{g5gLK|)>d=*XCDQ31ljw^=gEWnkvIQyf&Py*;s4Wx{zu~?#%Yff4ln79 zaCydd$A^J#2C_XQ_&<19#o5}w6Ps5(f76p=D`h)>NL8iFi&6*&ptbL>Sf-Ku^~A42 z1{3cXrljQvCx%z0Dr}~D`g6Uy)}_r_1eJ?tk4}7_$EzvRZAP_>60)m4!YN)BCbO;F zMAyo(r?K%njUF-Dw|!Re1IeNRH`8s}wJklC5Y4BBDn|5b*ht4v_RY;Trv26b^f>~&d zet>BcffYWQTu{MF^k<1T@Eb5j*rO1)wRwuCIS#x4k5De#L7T<==k>GQ+@s{|wZEfkx!* zsn$Phpi`*`&(UV~?8ZMPf54r9sn$-bR4@x+WS6_;cXu{o%SV%0Dqg3dc3lwpDPdMY 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

  • Esg6n}8& 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 N@@h8=uHSz2zW}aHd6EDC literal 14033 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 VhW#)q1Z)CB+*iMcRe13HzX8cb%isV2 literal 12669 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 diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-end-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-end-1-snap.png index 74f643fa836bcc8b36eb5632c6be77a5d16c3a42..7efc5689bf6fdad476f1db65d90fd6d758e205a8 100644 GIT binary patch literal 12667 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 E4Tc!MF8}}l literal 12669 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 NMpE%f!Q*GI{|9uaxHJF& diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-start-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-start-1-snap.png index 29e5ad321db4fcf935a2ee84455e41a861ae3565..bebc645842ce37c8d8ca87a1b42becd0f7dedad7 100644 GIT binary patch delta 10294 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 B%#Z*8 diff --git a/integration/tests/__image_snapshots__/interactions-test-ts-interactions-tooltips-should-render-corrent-tooltip-in-dark-theme-1-snap.png b/integration/tests/__image_snapshots__/interactions-test-ts-interactions-tooltips-should-render-corrent-tooltip-in-dark-theme-1-snap.png index 85dac90e5ac2ded941def11ed68281506fc8a702..3bd3a2c5a830307331939671f61868f95586b6d2 100644 GIT binary patch delta 45312 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

    8#mS$^+XJyDChZ2tKnQF^n1{K6y>X`>z4F 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 Kr#X*xKl~4{zC^eH diff --git a/src/chart_types/xy_chart/annotations/line/dimensions.integration.test.ts b/src/chart_types/xy_chart/annotations/line/dimensions.integration.test.ts index c511134cbc..b4c0f97d67 100644 --- a/src/chart_types/xy_chart/annotations/line/dimensions.integration.test.ts +++ b/src/chart_types/xy_chart/annotations/line/dimensions.integration.test.ts @@ -17,6 +17,7 @@ * under the License. */ +import { MockAnnotationLineProps } from '../../../../mocks/annotations/annotations'; import { MockSeriesSpec, MockAnnotationSpec, MockGlobalSpec } from '../../../../mocks/specs'; import { MockStore } from '../../../../mocks/store'; import { ScaleType } from '../../../../scales/constants'; @@ -51,14 +52,15 @@ function expectAnnotationAtPosition( MockStore.addSpecs([settings, ...specs, annotation], store); const annotations = computeAnnotationDimensionsSelector(store.getState()); expect(annotations.get(annotation.id)).toEqual([ - { + MockAnnotationLineProps.default({ details: { detailsText: undefined, headerText: `${indexPosition}` }, linePathPoints: { - start: { x1: expectedLinePosition, y1: 0 }, - end: { x2: expectedLinePosition, y2: 100 }, + x1: expectedLinePosition, + y1: 0, + x2: expectedLinePosition, + y2: 100, }, - marker: undefined, - }, + }), ]); } @@ -144,14 +146,15 @@ describe('Render vertical line annotation within', () => { MockStore.addSpecs([settings, spec, annotation], store); const annotations = computeAnnotationDimensionsSelector(store.getState()); expect(annotations.get(annotation.id)).toEqual([ - { + MockAnnotationLineProps.default({ linePathPoints: { - start: { x1: 95, y1: 0 }, - end: { x2: 95, y2: 100 }, + x1: 95, + y1: 0, + x2: 95, + y2: 100, }, details: { detailsText: 'foo', headerText: '9.5' }, - marker: undefined, - }, + }), ]); }); }); diff --git a/src/chart_types/xy_chart/annotations/line/dimensions.test.ts b/src/chart_types/xy_chart/annotations/line/dimensions.test.ts new file mode 100644 index 0000000000..0a873331c4 --- /dev/null +++ b/src/chart_types/xy_chart/annotations/line/dimensions.test.ts @@ -0,0 +1,729 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { MockAnnotationLineProps } from '../../../../mocks/annotations/annotations'; +import { MockAnnotationSpec, MockGlobalSpec, MockSeriesSpec } from '../../../../mocks/specs'; +import { MockStore } from '../../../../mocks/store'; +import { ScaleType } from '../../../../scales/constants'; +import { Position } from '../../../../utils/commons'; +import { AnnotationId } from '../../../../utils/ids'; +import { DEFAULT_ANNOTATION_LINE_STYLE } from '../../../../utils/themes/theme'; +import { computeAnnotationDimensionsSelector } from '../../state/selectors/compute_annotations'; +import { AnnotationDomainTypes } from '../../utils/specs'; +import { AnnotationDimensions } from '../types'; +import { AnnotationLineProps } from './types'; + +describe('Annotation utils', () => { + const groupId = 'foo-group'; + + const continuousBarChart = MockSeriesSpec.bar({ + xScaleType: ScaleType.Linear, + groupId, + data: [ + { x: 0, y: 0 }, + { x: 2, y: 0 }, + { x: 3, y: 10 }, + { x: 4, y: 5 }, + { x: 9, y: 10 }, + ], + }); + + const ordinalBarChart = MockSeriesSpec.bar({ + xScaleType: ScaleType.Ordinal, + groupId, + data: [ + { x: 'a', y: 1 }, + { x: 'b', y: 0 }, + { x: 'c', y: 10 }, + { x: 'd', y: 5 }, + ], + }); + + const verticalAxisSpec = MockGlobalSpec.axis({ + id: 'vertical_axis', + groupId, + hide: false, + showOverlappingTicks: false, + showOverlappingLabels: false, + position: Position.Left, + showGridLines: true, + }); + + test('should compute line annotation in x ordinal scale', () => { + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins(); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo', + groupId, + domainType: AnnotationDomainTypes.YDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + }); + + const rectAnnotation = MockAnnotationSpec.rect({ + id: 'rect', + groupId, + dataValues: [{ coordinates: { x0: 'a', x1: 'b', y0: 3, y1: 5 } }], + }); + + MockStore.addSpecs([settings, ordinalBarChart, lineAnnotation, rectAnnotation], store); + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + + const expectedDimensions = new Map(); + expectedDimensions.set('foo', [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 0, + y1: 80, + x2: 100, + y2: 80, + }, + details: { detailsText: 'foo', headerText: '2' }, + }), + ]); + expectedDimensions.set('rect', [ + { + details: undefined, + rect: { x: 0, y: 50, width: 50, height: 20 }, + panel: { top: 0, left: 0, width: 100, height: 100 }, + }, + ]); + + expect(dimensions).toEqual(expectedDimensions); + }); + + test('should compute line annotation dimensions also with missing axis', () => { + const store = MockStore.default({ width: 10, height: 20, top: 0, left: 0 }); + const settings = MockGlobalSpec.settingsNoMargins(); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo', + domainType: AnnotationDomainTypes.YDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + groupId, + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs([settings, ordinalBarChart, lineAnnotation], store); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + expect(dimensions.size).toEqual(1); + }); + + test('should compute line annotation dimensions for yDomain on a yScale (chartRotation 0, left axis)', () => { + const panel = { width: 10, height: 100, top: 0, left: 0 }; + const store = MockStore.default(panel); + const settings = MockGlobalSpec.settingsNoMargins(); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo-line', + domainType: AnnotationDomainTypes.YDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + groupId, + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs( + [ + settings, + ordinalBarChart, + lineAnnotation, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + hide: true, + }), + ], + store, + ); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + + const expectedDimensions: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 0, + y1: 80, + x2: 10, + y2: 80, + }, + panel, + details: { detailsText: 'foo', headerText: '2' }, + }), + ]; + expect(dimensions.get('foo-line')).toEqual(expectedDimensions); + }); + + test('should compute line annotation dimensions for yDomain on a yScale (chartRotation 0, right axis)', () => { + const store = MockStore.default({ width: 10, height: 100, top: 0, left: 0 }); + const settings = MockGlobalSpec.settingsNoMargins(); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo-line', + domainType: AnnotationDomainTypes.YDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + groupId, + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs( + [ + settings, + ordinalBarChart, + lineAnnotation, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + position: Position.Right, + hide: true, + }), + ], + store, + ); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + + const expectedDimensions: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 0, + y1: 80, + x2: 10, + y2: 80, + }, + panel: { width: 10, height: 100, top: 0, left: 0 }, + details: { detailsText: 'foo', headerText: '2' }, + }), + ]; + expect(dimensions.get('foo-line')).toEqual(expectedDimensions); + }); + + test('should compute line annotation dimensions for yDomain on a yScale (chartRotation 90)', () => { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + const settings = MockGlobalSpec.settingsNoMargins({ rotation: 90 }); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo-line', + domainType: AnnotationDomainTypes.YDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + groupId, + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs( + [ + settings, + ordinalBarChart, + lineAnnotation, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + hide: true, + }), + ], + store, + ); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + + const expectedDimensions: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 0, + y1: 80, + x2: 100, + y2: 80, + }, + details: { detailsText: 'foo', headerText: '2' }, + }), + ]; + expect(dimensions.get('foo-line')).toEqual(expectedDimensions); + }); + + test('should not compute line annotation dimensions for yDomain if no corresponding yScale', () => { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + const settings = MockGlobalSpec.settingsNoMargins({ rotation: 0 }); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo-line', + groupId: 'other-group', + domainType: AnnotationDomainTypes.YDomain, + dataValues: [], + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs( + [ + settings, + ordinalBarChart, + lineAnnotation, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + hide: true, + }), + ], + store, + ); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + + expect(dimensions.size).toEqual(0); + }); + + test('should compute line annotation dimensions for xDomain (chartRotation 0, ordinal scale)', () => { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + const settings = MockGlobalSpec.settingsNoMargins({ rotation: 0 }); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo-line', + groupId: 'other-group', + domainType: AnnotationDomainTypes.XDomain, + dataValues: [{ dataValue: 'a', details: 'foo' }], + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs( + [ + settings, + ordinalBarChart, + lineAnnotation, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + position: Position.Bottom, + hide: true, + }), + ], + store, + ); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + + const expectedDimensions: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 12.5, + y1: 0, + x2: 12.5, + y2: 100, + }, + details: { detailsText: 'foo', headerText: 'a' }, + }), + ]; + expect(dimensions.get('foo-line')).toEqual(expectedDimensions); + }); + + test('should compute line annotation dimensions for xDomain (chartRotation 0, continuous scale, top axis)', () => { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + const settings = MockGlobalSpec.settingsNoMargins({ rotation: 0 }); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo-line', + groupId: 'other-group', + domainType: AnnotationDomainTypes.XDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs( + [ + settings, + continuousBarChart, + lineAnnotation, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + position: Position.Top, + hide: true, + }), + ], + store, + ); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + const expectedDimensions: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 25, + y1: 0, + x2: 25, + y2: 100, + }, + details: { detailsText: 'foo', headerText: '2' }, + }), + ]; + expect(dimensions.get('foo-line')).toEqual(expectedDimensions); + }); + + test('should compute line annotation dimensions for xDomain (chartRotation 0, continuous scale, bottom axis)', () => { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + const settings = MockGlobalSpec.settingsNoMargins({ rotation: 0 }); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo-line', + groupId: 'other-group', + domainType: AnnotationDomainTypes.XDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs( + [ + settings, + continuousBarChart, + lineAnnotation, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + position: Position.Bottom, + hide: true, + }), + ], + store, + ); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + const expectedDimensions: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 25, + y1: 0, + x2: 25, + y2: 100, + }, + details: { detailsText: 'foo', headerText: '2' }, + }), + ]; + expect(dimensions.get('foo-line')).toEqual(expectedDimensions); + }); + + test('should compute line annotation dimensions for xDomain on a xScale (chartRotation 90, ordinal scale)', () => { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + const settings = MockGlobalSpec.settingsNoMargins({ rotation: 0 }); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo-line', + groupId: 'other-group', + domainType: AnnotationDomainTypes.XDomain, + dataValues: [{ dataValue: 'a', details: 'foo' }], + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs( + [ + settings, + ordinalBarChart, + lineAnnotation, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + position: Position.Top, + hide: true, + }), + ], + store, + ); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + const expectedDimensions: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 12.5, + y1: 0, + x2: 12.5, + y2: 100, + }, + details: { detailsText: 'foo', headerText: 'a' }, + }), + ]; + expect(dimensions.get('foo-line')).toEqual(expectedDimensions); + }); + + test('should compute line annotation dimensions for xDomain on a xScale (chartRotation 90, continuous scale)', () => { + const panel = { width: 100, height: 50, top: 0, left: 0 }; + const store = MockStore.default(panel); + const settings = MockGlobalSpec.settingsNoMargins({ rotation: 90 }); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo-line', + groupId: 'other-group', + domainType: AnnotationDomainTypes.XDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs( + [ + settings, + continuousBarChart, + lineAnnotation, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + position: Position.Top, + hide: true, + }), + ], + store, + ); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + const expectedDimensions: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 12.5, + y1: 0, + x2: 12.5, + y2: 100, + }, + panel, + details: { detailsText: 'foo', headerText: '2' }, + }), + ]; + expect(dimensions.get('foo-line')).toEqual(expectedDimensions); + }); + + test('should compute line annotation dimensions for xDomain on a xScale (chartRotation -90, continuous scale)', () => { + const panel = { width: 100, height: 50, top: 0, left: 0 }; + const store = MockStore.default(panel); + const settings = MockGlobalSpec.settingsNoMargins({ rotation: -90 }); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo-line', + groupId: 'other-group', + domainType: AnnotationDomainTypes.XDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs( + [ + settings, + continuousBarChart, + lineAnnotation, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + position: Position.Top, + hide: true, + }), + ], + store, + ); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + const expectedDimensions: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 12.5, + y1: 0, + x2: 12.5, + y2: 100, + }, + panel, + details: { detailsText: 'foo', headerText: '2' }, + }), + ]; + expect(dimensions.get('foo-line')).toEqual(expectedDimensions); + }); + + test('should compute line annotation dimensions for xDomain (chartRotation 180, continuous scale, top axis)', () => { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + const settings = MockGlobalSpec.settingsNoMargins({ rotation: 180 }); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo-line', + groupId: 'other-group', + domainType: AnnotationDomainTypes.XDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs( + [ + settings, + continuousBarChart, + lineAnnotation, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + position: Position.Top, + hide: true, + }), + ], + store, + ); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + const expectedDimensions: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 25, + y1: 0, + x2: 25, + y2: 100, + }, + details: { detailsText: 'foo', headerText: '2' }, + }), + ]; + expect(dimensions.get('foo-line')).toEqual(expectedDimensions); + }); + + test('should compute line annotation dimensions for xDomain (chartRotation 180, continuous scale, bottom axis)', () => { + const panel = { width: 100, height: 50, top: 0, left: 0 }; + const store = MockStore.default(panel); + const settings = MockGlobalSpec.settingsNoMargins({ rotation: 180 }); + + const lineAnnotation = MockAnnotationSpec.line({ + id: 'foo-line', + groupId: 'other-group', + domainType: AnnotationDomainTypes.XDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs( + [ + settings, + continuousBarChart, + lineAnnotation, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + position: Position.Bottom, + hide: true, + }), + ], + store, + ); + + const dimensions = computeAnnotationDimensionsSelector(store.getState()); + const expectedDimensions: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 25, + y1: 0, + x2: 25, + y2: 50, + }, + panel, + details: { detailsText: 'foo', headerText: '2' }, + }), + ]; + expect(dimensions.get('foo-line')).toEqual(expectedDimensions); + }); + test('should not compute annotation line values for invalid data values or AnnotationSpec.hideLines', () => { + let store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + const settings = MockGlobalSpec.settingsNoMargins({ rotation: 180 }); + + const annotationId = 'foo-line'; + const invalidXLineAnnotation = MockAnnotationSpec.line({ + id: annotationId, + domainType: AnnotationDomainTypes.XDomain, + dataValues: [{ dataValue: 'e', details: 'foo' }], + groupId, + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + MockStore.addSpecs([settings, continuousBarChart, invalidXLineAnnotation], store); + const emptyXDimensions = computeAnnotationDimensionsSelector(store.getState()); + + expect(emptyXDimensions.get('foo-line')).toHaveLength(0); + + const invalidStringXLineAnnotation = MockAnnotationSpec.line({ + id: annotationId, + domainType: AnnotationDomainTypes.XDomain, + dataValues: [{ dataValue: '', details: 'foo' }], + groupId, + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + store = MockStore.default({ width: 100, height: 50, top: 0, left: 0 }); + MockStore.addSpecs([settings, continuousBarChart, invalidStringXLineAnnotation], store); + + const invalidStringXDimensions = computeAnnotationDimensionsSelector(store.getState()); + + expect(invalidStringXDimensions.get('foo-line')).toHaveLength(0); + + const outOfBoundsXLineAnnotation = MockAnnotationSpec.line({ + id: annotationId, + domainType: AnnotationDomainTypes.XDomain, + dataValues: [{ dataValue: -999, details: 'foo' }], + groupId, + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + store = MockStore.default({ width: 100, height: 50, top: 0, left: 0 }); + MockStore.addSpecs([settings, continuousBarChart, outOfBoundsXLineAnnotation], store); + + const emptyOutOfBoundsXDimensions = computeAnnotationDimensionsSelector(store.getState()); + + expect(emptyOutOfBoundsXDimensions.get('foo-line')).toHaveLength(0); + + const invalidYLineAnnotation = MockAnnotationSpec.line({ + id: annotationId, + domainType: AnnotationDomainTypes.YDomain, + dataValues: [{ dataValue: 'e', details: 'foo' }], + groupId, + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + store = MockStore.default({ width: 100, height: 50, top: 0, left: 0 }); + MockStore.addSpecs([settings, continuousBarChart, invalidYLineAnnotation], store); + + const emptyOutOfBoundsYDimensions = computeAnnotationDimensionsSelector(store.getState()); + + expect(emptyOutOfBoundsYDimensions.get('foo-line')).toHaveLength(0); + + const outOfBoundsYLineAnnotation = MockAnnotationSpec.line({ + id: annotationId, + domainType: AnnotationDomainTypes.YDomain, + dataValues: [{ dataValue: -999, details: 'foo' }], + groupId, + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + store = MockStore.default({ width: 100, height: 50, top: 0, left: 0 }); + MockStore.addSpecs([settings, continuousBarChart, outOfBoundsYLineAnnotation], store); + + const outOfBoundsYAnn = computeAnnotationDimensionsSelector(store.getState()); + + expect(outOfBoundsYAnn.get('foo-line')).toHaveLength(0); + + const invalidStringYLineAnnotation = MockAnnotationSpec.line({ + id: annotationId, + domainType: AnnotationDomainTypes.YDomain, + dataValues: [{ dataValue: '', details: 'foo' }], + groupId, + style: DEFAULT_ANNOTATION_LINE_STYLE, + }); + + store = MockStore.default({ width: 100, height: 50, top: 0, left: 0 }); + MockStore.addSpecs([settings, continuousBarChart, invalidStringYLineAnnotation], store); + + const invalidStringYDimensions = computeAnnotationDimensionsSelector(store.getState()); + + expect(invalidStringYDimensions.get('foo-line')).toHaveLength(0); + + const validHiddenAnnotation = MockAnnotationSpec.line({ + id: annotationId, + domainType: AnnotationDomainTypes.XDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + groupId, + style: DEFAULT_ANNOTATION_LINE_STYLE, + hideLines: true, + }); + + store = MockStore.default({ width: 100, height: 50, top: 0, left: 0 }); + MockStore.addSpecs([settings, continuousBarChart, validHiddenAnnotation], store); + + const hiddenAnnotationDimensions = computeAnnotationDimensionsSelector(store.getState()); + + expect(hiddenAnnotationDimensions.size).toBe(0); + }); +}); diff --git a/src/chart_types/xy_chart/annotations/line/dimensions.ts b/src/chart_types/xy_chart/annotations/line/dimensions.ts index 240628bd6a..3a17155010 100644 --- a/src/chart_types/xy_chart/annotations/line/dimensions.ts +++ b/src/chart_types/xy_chart/annotations/line/dimensions.ts @@ -17,16 +17,19 @@ * under the License. */ +import { Line } from '../../../../geoms/types'; import { Scale } from '../../../../scales'; import { isContinuousScale, isBandScale } from '../../../../scales/types'; -import { Position, Rotation } from '../../../../utils/commons'; -import { Dimensions } from '../../../../utils/dimensions'; +import { isNil, Position, Rotation } from '../../../../utils/commons'; +import { Dimensions, Size } from '../../../../utils/dimensions'; import { GroupId } from '../../../../utils/ids'; +import { SmallMultipleScales } from '../../state/selectors/compute_small_multiple_scales'; import { isHorizontalRotation } from '../../state/utils/common'; import { computeXScaleOffset } from '../../state/utils/utils'; +import { getPanelSize } from '../../utils/panel'; import { AnnotationDomainTypes, LineAnnotationSpec, LineAnnotationDatum } from '../../utils/specs'; import { AnnotationMarker } from '../types'; -import { AnnotationLineProps, AnnotationLinePathPoints } from './types'; +import { AnnotationLineProps } from './types'; /** @internal */ export const DEFAULT_LINE_OVERFLOW = 0; @@ -34,8 +37,8 @@ export const DEFAULT_LINE_OVERFLOW = 0; function computeYDomainLineAnnotationDimensions( annotationSpec: LineAnnotationSpec, yScale: Scale, + { vertical, horizontal }: SmallMultipleScales, chartRotation: Rotation, - chartDimensions: Dimensions, lineColor: string, axisPosition?: Position, ): AnnotationLineProps[] { @@ -51,6 +54,9 @@ function computeYDomainLineAnnotationDimensions( const anchorPosition = getAnchorPosition(false, isHorizontalChartRotation, specMarkerPosition, axisPosition); const lineProps: AnnotationLineProps[] = []; + const [domainStart, domainEnd] = yScale.domain; + + const panelSize = getPanelSize({ vertical, horizontal }); dataValues.forEach((datum: LineAnnotationDatum) => { const { dataValue } = datum; @@ -66,39 +72,56 @@ function computeYDomainLineAnnotationDimensions( return; } - const [domainStart, domainEnd] = yScale.domain; // avoid rendering annotation with values outside the scale domain if (dataValue < domainStart || dataValue > domainEnd) { return; } - const markerPosition = getMarkerPositionForYAnnotation( - chartDimensions, - chartRotation, - markerDimensions, - anchorPosition, - annotationValueYPosition, - ); - const linePathPoints = getYLinePath(chartDimensions, annotationValueYPosition, chartRotation); - - const annotationMarker: AnnotationMarker | undefined = marker - ? { - icon: marker, - color: lineColor, - dimension: { ...markerDimensions }, - position: markerPosition, - } - : undefined; - const lineProp: AnnotationLineProps = { - linePathPoints, - marker: annotationMarker, - details: { - detailsText: datum.details, - headerText: datum.header || dataValue.toString(), - }, - }; - - lineProps.push(lineProp); + vertical.domain.forEach((verticalValue) => { + horizontal.domain.forEach((horizontalValue) => { + const topPos = vertical.scaleOrThrow(verticalValue); + const leftPos = horizontal.scaleOrThrow(horizontalValue); + + const width = isHorizontalChartRotation ? horizontal.bandwidth : vertical.bandwidth; + const height = isHorizontalChartRotation ? vertical.bandwidth : horizontal.bandwidth; + + const markerPosition = getMarkerPositionForYAnnotation( + panelSize, + chartRotation, + markerDimensions, + anchorPosition, + annotationValueYPosition, + ); + const linePathPoints = getYLinePath({ width, height }, annotationValueYPosition); + + const annotationMarker: AnnotationMarker | undefined = marker + ? { + icon: marker, + color: lineColor, + dimension: { ...markerDimensions }, + position: { + top: markerPosition.top, + left: markerPosition.left, + }, + } + : undefined; + const lineProp: AnnotationLineProps = { + linePathPoints, + marker: annotationMarker, + panel: { + ...panelSize, + top: topPos, + left: leftPos, + }, + details: { + detailsText: datum.details, + headerText: datum.header || dataValue.toString(), + }, + }; + + lineProps.push(lineProp); + }); + }); }); return lineProps; @@ -107,8 +130,8 @@ function computeYDomainLineAnnotationDimensions( function computeXDomainLineAnnotationDimensions( annotationSpec: LineAnnotationSpec, xScale: Scale, + { vertical, horizontal }: SmallMultipleScales, chartRotation: Rotation, - chartDimensions: Dimensions, lineColor: string, isHistogramMode: boolean, axisPosition?: Position, @@ -123,11 +146,12 @@ function computeXDomainLineAnnotationDimensions( const lineProps: AnnotationLineProps[] = []; const isHorizontalChartRotation = isHorizontalRotation(chartRotation); const anchorPosition = getAnchorPosition(true, isHorizontalChartRotation, specMarkerPosition, axisPosition); + const panelSize = getPanelSize({ vertical, horizontal }); dataValues.forEach((datum: LineAnnotationDatum) => { const { dataValue } = datum; let annotationValueXPosition = xScale.scale(dataValue); - if (annotationValueXPosition == null) { + if (isNil(annotationValueXPosition)) { return; } if (isContinuousScale(xScale) && typeof dataValue === 'number') { @@ -160,32 +184,54 @@ function computeXDomainLineAnnotationDimensions( return; } - const markerPosition = getMarkerPositionForXAnnotation( - chartDimensions, - chartRotation, - markerDimensions, - anchorPosition, - annotationValueXPosition, - ); - const linePathPoints = getXLinePath(chartDimensions, annotationValueXPosition, chartRotation); - - const annotationMarker: AnnotationMarker | undefined = marker - ? { - icon: marker, - color: lineColor, - dimension: { ...markerDimensions }, - position: markerPosition, + vertical.domain.forEach((verticalValue) => { + horizontal.domain.forEach((horizontalValue) => { + if (annotationValueXPosition == null) { + return; } - : undefined; - const lineProp: AnnotationLineProps = { - linePathPoints, - details: { - detailsText: datum.details, - headerText: datum.header || dataValue.toString(), - }, - marker: annotationMarker, - }; - lineProps.push(lineProp); + + const topPos = vertical.scaleOrThrow(verticalValue); + const leftPos = horizontal.scaleOrThrow(horizontalValue); + const width = isHorizontalChartRotation ? horizontal.bandwidth : vertical.bandwidth; + const height = isHorizontalChartRotation ? vertical.bandwidth : horizontal.bandwidth; + + const markerPosition = getMarkerPositionForXAnnotation( + panelSize, + chartRotation, + markerDimensions, + anchorPosition, + annotationValueXPosition, + ); + + const linePathPoints = getXLinePath({ width, height }, annotationValueXPosition); + + const annotationMarker: AnnotationMarker | undefined = marker + ? { + icon: marker, + color: lineColor, + dimension: { ...markerDimensions }, + position: { + top: markerPosition.top, + left: markerPosition.left, + }, + } + : undefined; + const lineProp: AnnotationLineProps = { + linePathPoints, + details: { + detailsText: datum.details, + headerText: datum.header || dataValue.toString(), + }, + marker: annotationMarker, + panel: { + ...panelSize, + top: topPos, + left: leftPos, + }, + }; + lineProps.push(lineProp); + }); + }); }); return lineProps; @@ -194,10 +240,10 @@ function computeXDomainLineAnnotationDimensions( /** @internal */ export function computeLineAnnotationDimensions( annotationSpec: LineAnnotationSpec, - chartDimensions: Dimensions, chartRotation: Rotation, yScales: Map, xScale: Scale, + smallMultipleScales: SmallMultipleScales, isHistogramMode: boolean, axisPosition?: Position, ): AnnotationLineProps[] | null { @@ -215,8 +261,8 @@ export function computeLineAnnotationDimensions( return computeXDomainLineAnnotationDimensions( annotationSpec, xScale, + smallMultipleScales, chartRotation, - chartDimensions, lineColor, isHistogramMode, axisPosition, @@ -232,8 +278,8 @@ export function computeLineAnnotationDimensions( return computeYDomainLineAnnotationDimensions( annotationSpec, yScale, + smallMultipleScales, chartRotation, - chartDimensions, lineColor, axisPosition, ); @@ -276,43 +322,28 @@ function getDefaultMarkerPositionFromAxis( return Position.Bottom; } -function getXLinePath( - { width, height }: Pick, - value: number, - rotation: Rotation, -): AnnotationLinePathPoints { +function getXLinePath({ height }: Size, value: number): Line { return { - start: { - x1: value, - y1: 0, - }, - end: { - x2: value, - y2: rotation === -90 || rotation === 90 ? width : height, - }, + x1: value, + y1: 0, + x2: value, + y2: height, }; } -function getYLinePath( - { width, height }: Pick, - value: number, - rotation: Rotation, -): AnnotationLinePathPoints { + +function getYLinePath({ width }: Size, value: number): Line { return { - start: { - x1: 0, - y1: value, - }, - end: { - x2: rotation === -90 || rotation === 90 ? height : width, - y2: value, - }, + x1: 0, + y1: value, + x2: width, + y2: value, }; } -function getMarkerPositionForXAnnotation( - { width, height }: Pick, +export function getMarkerPositionForXAnnotation( + { width, height }: Size, rotation: Rotation, - { width: mWidth, height: mHeight }: Pick, + { width: mWidth, height: mHeight }: Size, position: Position, value: number, ): Pick { @@ -342,9 +373,9 @@ function getMarkerPositionForXAnnotation( } function getMarkerPositionForYAnnotation( - { width, height }: Pick, + { width, height }: Size, rotation: Rotation, - { width: mWidth, height: mHeight }: Pick, + { width: mWidth, height: mHeight }: Size, position: Position, value: number, ): { @@ -364,7 +395,7 @@ function getMarkerPositionForYAnnotation( }; case Position.Top: return { - top: 0 - mHeight, + top: -mHeight, left: rotation === 90 ? width - value - mWidth / 2 : value - mWidth / 2, }; case Position.Bottom: diff --git a/src/chart_types/xy_chart/annotations/line/line.test.tsx b/src/chart_types/xy_chart/annotations/line/line.test.tsx index 8c6459050c..f06fa1e3ec 100644 --- a/src/chart_types/xy_chart/annotations/line/line.test.tsx +++ b/src/chart_types/xy_chart/annotations/line/line.test.tsx @@ -20,6 +20,7 @@ import React from 'react'; import { Store } from 'redux'; +import { MockAnnotationLineProps } from '../../../../mocks/annotations/annotations'; import { MockAnnotationSpec, MockGlobalSpec, MockSeriesSpec } from '../../../../mocks/specs'; import { MockStore } from '../../../../mocks/store'; import { ScaleType } from '../../../../scales/constants'; @@ -75,16 +76,12 @@ describe('annotation marker', () => { const dimensions = computeAnnotationDimensionsSelector(store.getState()); const expectedDimensions: AnnotationLineProps[] = [ - { + MockAnnotationLineProps.default({ linePathPoints: { - start: { - x1: 0, - y1: 80, - }, - end: { - x2: 100, - y2: 80, - }, + x1: 0, + y1: 80, + x2: 100, + y2: 80, }, details: { detailsText: 'foo', headerText: '2' }, @@ -94,7 +91,7 @@ describe('annotation marker', () => { dimension: { width: 0, height: 0 }, position: { left: -0, top: 80 }, }, - }, + }), ]; expect(dimensions.get(id)).toEqual(expectedDimensions); }); @@ -117,16 +114,12 @@ describe('annotation marker', () => { // so this position at 80 pixel right now, is a 20 pixel from top // when rotated 180 degrees const expectedDimensions: AnnotationLineProps[] = [ - { + MockAnnotationLineProps.default({ linePathPoints: { - start: { - x1: 0, - y1: 80, - }, - end: { - x2: 100, - y2: 80, - }, + x1: 0, + y1: 80, + x2: 100, + y2: 80, }, details: { detailsText: 'foo', headerText: '2' }, marker: { @@ -135,7 +128,7 @@ describe('annotation marker', () => { dimension: { width: 0, height: 0 }, position: { left: -0, top: 20 }, }, - }, + }), ]; expect(dimensions.get(id)).toEqual(expectedDimensions); }); @@ -154,17 +147,13 @@ describe('annotation marker', () => { const dimensions = computeAnnotationDimensionsSelector(store.getState()); const expectedDimensions: AnnotationLineProps[] = [ - { + MockAnnotationLineProps.default({ details: { detailsText: 'foo', headerText: '2' }, linePathPoints: { - start: { - x1: 20, - y1: 0, - }, - end: { - x2: 20, - y2: 100, - }, + x1: 20, + y1: 0, + x2: 20, + y2: 100, }, marker: { icon:

    , @@ -172,7 +161,7 @@ describe('annotation marker', () => { dimension: { width: 0, height: 0 }, position: { top: 100, left: 20 }, }, - }, + }), ]; expect(dimensions.get(id)).toEqual(expectedDimensions); }); diff --git a/src/chart_types/xy_chart/annotations/line/tooltip.test.ts b/src/chart_types/xy_chart/annotations/line/tooltip.test.ts new file mode 100644 index 0000000000..6a959e510b --- /dev/null +++ b/src/chart_types/xy_chart/annotations/line/tooltip.test.ts @@ -0,0 +1,364 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; + +import { ChartTypes } from '../../..'; +import { MockAnnotationLineProps, MockAnnotationRectProps } from '../../../../mocks/annotations/annotations'; +import { MockGlobalSpec } from '../../../../mocks/specs/specs'; +import { SpecTypes } from '../../../../specs/constants'; +import { Position, Rotation } from '../../../../utils/commons'; +import { Dimensions } from '../../../../utils/dimensions'; +import { AnnotationId } from '../../../../utils/ids'; +import { Point } from '../../../../utils/point'; +import { DEFAULT_ANNOTATION_LINE_STYLE } from '../../../../utils/themes/theme'; +import { + AnnotationDomainTypes, + AnnotationSpec, + AnnotationTypes, + AxisSpec, + LineAnnotationSpec, + RectAnnotationSpec, +} from '../../utils/specs'; +import { computeAnnotationTooltipState } from '../tooltip'; +import { AnnotationDimensions, AnnotationTooltipState } from '../types'; +import { computeLineAnnotationTooltipState } from './tooltip'; +import { AnnotationLineProps } from './types'; + +describe('Annotation tooltips', () => { + const groupId = 'foo-group'; + const chartDimensions: Dimensions = { + width: 10, + height: 20, + top: 5, + left: 15, + }; + const horizontalAxisSpec = MockGlobalSpec.axis({ + groupId, + position: Position.Bottom, + }); + const verticalAxisSpec = MockGlobalSpec.axis({ + groupId, + position: Position.Left, + }); + test('should compute the tooltip state for an annotation line', () => { + const cursorPosition: Point = { x: 16, y: 7 }; + const annotationLines: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 1, + y1: 2, + x2: 3, + y2: 4, + }, + marker: { + icon: React.createElement('div'), + color: 'red', + dimension: { width: 10, height: 10 }, + position: { top: 0, left: 0 }, + }, + }), + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 0, + y1: 10, + x2: 20, + y2: 10, + }, + marker: { + icon: React.createElement('div'), + color: 'red', + dimension: { width: 20, height: 20 }, + position: { top: 0, left: 0 }, + }, + }), + ]; + const localAxesSpecs: AxisSpec[] = []; + // missing annotation axis (xDomain) + const missingTooltipState = computeLineAnnotationTooltipState( + cursorPosition, + annotationLines, + groupId, + AnnotationDomainTypes.XDomain, + localAxesSpecs, + chartDimensions, + ); + + expect(missingTooltipState).toBeNull(); + + // add axis for xDomain annotation + localAxesSpecs.push(horizontalAxisSpec); + + const xDomainTooltipState = computeLineAnnotationTooltipState( + cursorPosition, + annotationLines, + groupId, + AnnotationDomainTypes.XDomain, + localAxesSpecs, + chartDimensions, + ); + const expectedXDomainTooltipState = { + isVisible: true, + annotationType: AnnotationTypes.Line, + anchor: { + height: 10, + left: 15, + top: 5, + width: 10, + }, + }; + expect(xDomainTooltipState).toMatchObject(expectedXDomainTooltipState); + + // rotated xDomain + const xDomainRotatedTooltipState = computeLineAnnotationTooltipState( + { x: 24, y: 23 }, + annotationLines, + groupId, + AnnotationDomainTypes.XDomain, + localAxesSpecs, + chartDimensions, + ); + const expectedXDomainRotatedTooltipState: AnnotationTooltipState = { + isVisible: true, + anchor: { + left: 15, + top: 5, + }, + annotationType: AnnotationTypes.Line, + }; + + expect(xDomainRotatedTooltipState).toMatchObject(expectedXDomainRotatedTooltipState); + + // add axis for yDomain annotation + localAxesSpecs.push(verticalAxisSpec); + + const yDomainTooltipState = computeLineAnnotationTooltipState( + cursorPosition, + annotationLines, + groupId, + AnnotationDomainTypes.YDomain, + localAxesSpecs, + chartDimensions, + ); + const expectedYDomainTooltipState: AnnotationTooltipState = { + isVisible: true, + anchor: { + left: 15, + top: 5, + }, + annotationType: AnnotationTypes.Line, + }; + + expect(yDomainTooltipState).toMatchObject(expectedYDomainTooltipState); + + const flippedYDomainTooltipState = computeLineAnnotationTooltipState( + { x: 24, y: 23 }, + annotationLines, + groupId, + AnnotationDomainTypes.YDomain, + localAxesSpecs, + chartDimensions, + ); + const expectedFlippedYDomainTooltipState: AnnotationTooltipState = { + isVisible: true, + anchor: { + left: 15, + top: 5, + }, + annotationType: AnnotationTypes.Line, + }; + + expect(flippedYDomainTooltipState).toMatchObject(expectedFlippedYDomainTooltipState); + + const rotatedYDomainTooltipState = computeLineAnnotationTooltipState( + { x: 25, y: 15 }, + annotationLines, + groupId, + AnnotationDomainTypes.YDomain, + localAxesSpecs, + chartDimensions, + ); + const expectedRotatedYDomainTooltipState: AnnotationTooltipState = { + isVisible: true, + anchor: { + left: 15, + top: 5, + }, + annotationType: AnnotationTypes.Line, + }; + + expect(rotatedYDomainTooltipState).toMatchObject(expectedRotatedYDomainTooltipState); + }); + + test('should compute the tooltip state for an annotation', () => { + const annotations: AnnotationSpec[] = []; + const annotationId = 'foo'; + const lineAnnotation: LineAnnotationSpec = { + chartType: ChartTypes.XYAxis, + specType: SpecTypes.Annotation, + annotationType: AnnotationTypes.Line, + id: annotationId, + domainType: AnnotationDomainTypes.YDomain, + dataValues: [{ dataValue: 2, details: 'foo' }], + groupId, + style: DEFAULT_ANNOTATION_LINE_STYLE, + }; + + const cursorPosition: Point = { x: 16, y: 7 }; + + const annotationLines: AnnotationLineProps[] = [ + MockAnnotationLineProps.default({ + linePathPoints: { + x1: 1, + y1: 2, + x2: 3, + y2: 4, + }, + marker: { + icon: React.createElement('div'), + color: 'red', + dimension: { width: 10, height: 10 }, + position: { top: 0, left: 0 }, + }, + }), + ]; + const chartRotation: Rotation = 0; + const localAxesSpecs: AxisSpec[] = []; + + const annotationDimensions = new Map(); + annotationDimensions.set(annotationId, annotationLines); + + // missing annotations + const missingSpecTooltipState = computeAnnotationTooltipState( + cursorPosition, + annotationDimensions, + annotations, + chartRotation, + localAxesSpecs, + chartDimensions, + ); + + expect(missingSpecTooltipState).toBe(null); + + // add valid annotation axis + annotations.push(lineAnnotation); + localAxesSpecs.push(verticalAxisSpec); + + // hide tooltipState + lineAnnotation.hideTooltips = true; + + const hideTooltipState = computeAnnotationTooltipState( + cursorPosition, + annotationDimensions, + annotations, + chartRotation, + localAxesSpecs, + chartDimensions, + ); + + expect(hideTooltipState).toBe(null); + + // show tooltipState, hide lines + lineAnnotation.hideTooltips = false; + lineAnnotation.hideLines = true; + + const hideLinesTooltipState = computeAnnotationTooltipState( + cursorPosition, + annotationDimensions, + annotations, + chartRotation, + localAxesSpecs, + chartDimensions, + ); + + expect(hideLinesTooltipState).toBe(null); + + // show tooltipState & lines + lineAnnotation.hideTooltips = false; + lineAnnotation.hideLines = false; + + const tooltipState = computeAnnotationTooltipState( + cursorPosition, + annotationDimensions, + annotations, + chartRotation, + localAxesSpecs, + chartDimensions, + ); + + const expectedTooltipState = { + isVisible: true, + annotationType: AnnotationTypes.Line, + anchor: { + height: 10, + left: 15, + top: 5, + width: 10, + }, + }; + + expect(tooltipState).toMatchObject(expectedTooltipState); + + // rect annotation tooltip + const annotationRectangle: RectAnnotationSpec = { + chartType: ChartTypes.XYAxis, + specType: SpecTypes.Annotation, + id: 'rect', + groupId, + annotationType: AnnotationTypes.Rectangle, + dataValues: [{ coordinates: { x0: 1, x1: 2, y0: 3, y1: 5 } }], + }; + + const rectAnnotations: RectAnnotationSpec[] = []; + rectAnnotations.push(annotationRectangle); + + annotationDimensions.set(annotationRectangle.id, [ + MockAnnotationRectProps.default({ rect: { x: 2, y: 3, width: 3, height: 5 } }), + ]); + + const rectTooltipState = computeAnnotationTooltipState( + { x: 18, y: 9 }, + annotationDimensions, + rectAnnotations, + chartRotation, + localAxesSpecs, + chartDimensions, + ); + + expect(rectTooltipState).toMatchObject({ + isVisible: true, + annotationType: AnnotationTypes.Rectangle, + anchor: { + left: 18, + top: 9, + }, + }); + annotationRectangle.hideTooltips = true; + + const rectHideTooltipState = computeAnnotationTooltipState( + { x: 3, y: 4 }, + annotationDimensions, + rectAnnotations, + chartRotation, + localAxesSpecs, + chartDimensions, + ); + + expect(rectHideTooltipState).toBe(null); + }); +}); diff --git a/src/chart_types/xy_chart/annotations/line/tooltip.ts b/src/chart_types/xy_chart/annotations/line/tooltip.ts index 5f35741472..d4112a8119 100644 --- a/src/chart_types/xy_chart/annotations/line/tooltip.ts +++ b/src/chart_types/xy_chart/annotations/line/tooltip.ts @@ -43,13 +43,13 @@ export function computeLineAnnotationTooltipState( if (!annotationAxis) { return null; } - + // get cursor point relative to the rendering area (within the chartDimension margins) const projectedPointer = getTransformedCursor(cursorPosition, chartDimensions, null, true); const totalAnnotationLines = annotationLines.length; for (let i = 0; i < totalAnnotationLines; i++) { const line = annotationLines[i]; - if (isWithinLineMarkerBounds(projectedPointer, line.marker)) { + if (isWithinLineMarkerBounds(projectedPointer, line.panel, line.marker)) { const position = invertTranformedCursor( { x: line.marker.position.left, @@ -63,8 +63,8 @@ export function computeLineAnnotationTooltipState( annotationType: AnnotationTypes.Line, isVisible: true, anchor: { - top: position.y, - left: position.x, + top: position.y + line.panel.top, + left: position.x + line.panel.left, ...line.marker.dimension, }, ...(line.details && { header: line.details.headerText }), @@ -79,15 +79,26 @@ export function computeLineAnnotationTooltipState( /** * Checks if the cursorPosition is within the line annotation marker * @param cursorPosition the cursor position relative to the projected area + * @param panel * @param marker the line annotation marker */ -function isWithinLineMarkerBounds(cursorPosition: Point, marker?: AnnotationMarker): marker is AnnotationMarker { +function isWithinLineMarkerBounds( + cursorPosition: Point, + panel: Dimensions, + marker?: AnnotationMarker, +): marker is AnnotationMarker { if (!marker) { return false; } - - const { top, left } = marker.position; - const { width, height } = marker.dimension; - const markerRect: Bounds = { startX: left, startY: top, endX: left + width, endY: top + height }; + const { + position: { top, left }, + dimension: { width, height }, + } = marker; + const markerRect: Bounds = { + startX: left + panel.left, + startY: top + panel.top, + endX: left + panel.left + width, + endY: top + panel.top + height, + }; return isWithinRectBounds(cursorPosition, markerRect); } diff --git a/src/chart_types/xy_chart/annotations/line/types.ts b/src/chart_types/xy_chart/annotations/line/types.ts index 0885f41f17..5ad78b9cfb 100644 --- a/src/chart_types/xy_chart/annotations/line/types.ts +++ b/src/chart_types/xy_chart/annotations/line/types.ts @@ -17,31 +17,17 @@ * under the License. */ +import { Line } from '../../../../geoms/types'; +import { Dimensions } from '../../../../utils/dimensions'; import { AnnotationDetails, AnnotationMarker } from '../types'; -/** - * Start and end points of a line annotation - * @internal - */ -export interface AnnotationLinePathPoints { - /** x1,y1 the start point anchored to the linked axis */ - start: { - x1: number; - y1: number; - }; - /** x2,y2 the end point */ - end: { - x2: number; - y2: number; - }; -} - /** @internal */ export interface AnnotationLineProps { /** * The path points of a line annotation */ - linePathPoints: AnnotationLinePathPoints; + linePathPoints: Line; details: AnnotationDetails; marker?: AnnotationMarker; + panel: Dimensions; } diff --git a/src/chart_types/xy_chart/annotations/rect/dimensions.test.ts b/src/chart_types/xy_chart/annotations/rect/dimensions.test.ts new file mode 100644 index 0000000000..1eb90d7d9f --- /dev/null +++ b/src/chart_types/xy_chart/annotations/rect/dimensions.test.ts @@ -0,0 +1,105 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { MockAnnotationSpec, MockGlobalSpec, MockSeriesSpec } from '../../../../mocks/specs/specs'; +import { MockStore } from '../../../../mocks/store/store'; +import { ScaleType } from '../../../../scales/constants'; +import { computeAnnotationDimensionsSelector } from '../../state/selectors/compute_annotations'; +import { isWithinRectBounds } from './dimensions'; +import { AnnotationRectProps } from './types'; + +describe('Rect Annotation Dimensions', () => { + const continuousBarChart = MockSeriesSpec.area({ + xScaleType: ScaleType.Linear, + data: [ + { x: 0, y: 0 }, + { x: 2, y: 0 }, + { x: 3, y: 10 }, + { x: 4, y: 5 }, + { x: 10, y: 10 }, + ], + }); + + test('should skip computing rectangle annotation dimensions when annotation data invalid', () => { + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins(); + + const annotationRectangle = MockAnnotationSpec.rect({ + id: 'rect', + dataValues: [ + { coordinates: { x0: 1, x1: 2, y0: -10, y1: 5 } }, + { coordinates: { x0: null, x1: null, y0: null, y1: null } }, + ], + }); + + MockStore.addSpecs([settings, continuousBarChart, annotationRectangle], store); + const skippedInvalid = computeAnnotationDimensionsSelector(store.getState()); + expect(skippedInvalid.size).toBe(1); + }); + + test('should compute rectangle dimensions shifted for histogram mode', () => { + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins(); + + const annotationRectangle = MockAnnotationSpec.rect({ + id: 'rect', + dataValues: [ + { coordinates: { x0: 1, x1: null, y0: null, y1: null } }, + { coordinates: { x0: null, x1: 1, y0: null, y1: null } }, + { coordinates: { x0: null, x1: null, y0: 1, y1: null } }, + { coordinates: { x0: null, x1: null, y0: null, y1: 1 } }, + ], + }); + + MockStore.addSpecs([settings, continuousBarChart, annotationRectangle], store); + const [dims1, dims2, dims3, dims4] = computeAnnotationDimensionsSelector(store.getState()).get( + 'rect', + ) as AnnotationRectProps[]; + + expect(dims1.rect.x).toBe(10); + expect(dims1.rect.width).toBeCloseTo(90); + expect(dims1.rect.y).toBe(0); + expect(dims1.rect.height).toBe(100); + + expect(dims2.rect.x).toBe(0); + expect(dims2.rect.width).toBe(10); + expect(dims2.rect.y).toBe(0); + expect(dims2.rect.height).toBe(100); + + expect(dims3.rect.x).toBe(0); + expect(dims3.rect.width).toBe(100); + expect(dims3.rect.y).toBe(0); + expect(dims3.rect.height).toBe(90); + + expect(dims4.rect.x).toBe(0); + expect(dims4.rect.width).toBeCloseTo(100); + expect(dims4.rect.y).toBe(90); + expect(dims4.rect.height).toBe(10); + }); + + test('should determine if a point is within a rectangle annotation', () => { + expect(isWithinRectBounds({ x: 3, y: 4 }, { startX: 2, endX: 4, startY: 3, endY: 5 })).toBe(true); + // TODO check I've a doubt that this should be an error + expect(isWithinRectBounds({ x: 3, y: 4 }, { startX: 2, endX: 4, startY: 5, endY: 3 })).toBe(false); + expect(isWithinRectBounds({ x: 3, y: 4 }, { startX: 2, endX: 4, startY: 5, endY: 6 })).toBe(false); + + expect(isWithinRectBounds({ x: 3, y: 4 }, { startX: 4, endX: 5, startY: 3, endY: 5 })).toBe(false); + expect(isWithinRectBounds({ x: 3, y: 4 }, { startX: 4, endX: 2, startY: 3, endY: 5 })).toBe(false); + }); +}); diff --git a/src/chart_types/xy_chart/annotations/rect/dimensions.ts b/src/chart_types/xy_chart/annotations/rect/dimensions.ts index 783eca1512..f5ff20195e 100644 --- a/src/chart_types/xy_chart/annotations/rect/dimensions.ts +++ b/src/chart_types/xy_chart/annotations/rect/dimensions.ts @@ -20,10 +20,11 @@ import { Scale, ScaleBand, ScaleContinuous } from '../../../../scales'; import { isBandScale, isContinuousScale } from '../../../../scales/types'; import { isDefined } from '../../../../utils/commons'; -import { Dimensions } from '../../../../utils/dimensions'; import { GroupId } from '../../../../utils/ids'; import { Point } from '../../../../utils/point'; import { PrimitiveValue } from '../../../partition_chart/layout/utils/group_by_rollup'; +import { SmallMultipleScales } from '../../state/selectors/compute_small_multiple_scales'; +import { getPanelSize } from '../../utils/panel'; import { RectAnnotationDatum, RectAnnotationSpec } from '../../utils/specs'; import { Bounds } from '../types'; import { AnnotationRectProps } from './types'; @@ -39,16 +40,17 @@ export function isWithinRectBounds({ x, y }: Point, { startX, endX, startY, endY /** @internal */ export function computeRectAnnotationDimensions( annotationSpec: RectAnnotationSpec, - chartDimensions: Dimensions, yScales: Map, xScale: Scale, + smallMultiplesScales: SmallMultipleScales, isHistogram: boolean = false, ): AnnotationRectProps[] | null { const { dataValues } = annotationSpec; const { groupId } = annotationSpec; const yScale = yScales.get(groupId); - const rectsProps: AnnotationRectProps[] = []; + const rectsProps: Omit[] = []; + const panelSize = getPanelSize(smallMultiplesScales); dataValues.forEach((dataValue: RectAnnotationDatum) => { const { x0: initialX0, x1: initialX1, y0: initialY0, y1: initialY1 } = dataValue.coordinates; @@ -80,7 +82,7 @@ export function computeRectAnnotationDimensions( const rectDimensions = { ...xAndWidth, y: 0, - height: chartDimensions.height, + height: panelSize.height, }; rectsProps.push({ @@ -106,7 +108,7 @@ export function computeRectAnnotationDimensions( // if the annotation height is 0 override it with the height from chart dimension and if the values in the domain are the same if (height === 0 && yScale.domain.length === 2 && yScale.domain[0] === yScale.domain[1]) { // eslint-disable-next-line prefer-destructuring - height = chartDimensions.height; + height = panelSize.height; scaledY1 = 0; } @@ -122,7 +124,20 @@ export function computeRectAnnotationDimensions( }); }); - return rectsProps; + return rectsProps.reduce((acc, props) => { + const duplicated: AnnotationRectProps[] = []; + smallMultiplesScales.vertical.domain.forEach((vDomainValue) => { + smallMultiplesScales.horizontal.domain.forEach((hDomainValue) => { + const panel = { + ...panelSize, + top: smallMultiplesScales.vertical.scaleOrThrow(vDomainValue), + left: smallMultiplesScales.horizontal.scaleOrThrow(hDomainValue), + }; + duplicated.push({ ...props, panel }); + }); + }); + return [...acc, ...duplicated]; + }, []); } function scaleXonBandScale( diff --git a/src/chart_types/xy_chart/annotations/rect/tooltip.test.ts b/src/chart_types/xy_chart/annotations/rect/tooltip.test.ts new file mode 100644 index 0000000000..f4012ea97b --- /dev/null +++ b/src/chart_types/xy_chart/annotations/rect/tooltip.test.ts @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Dimensions } from '../../../../utils/dimensions'; +import { AnnotationTypes } from '../../utils/specs'; +import { AnnotationTooltipState } from '../types'; +import { computeRectAnnotationTooltipState } from './tooltip'; + +describe('Rect annotation tooltip', () => { + test('should compute tooltip state for rect annotation', () => { + const chartDimensions: Dimensions = { + width: 10, + height: 20, + top: 5, + left: 15, + }; + const cursorPosition = { x: 18, y: 9 }; + const annotationRects = [ + { rect: { x: 2, y: 3, width: 3, height: 5 }, panel: { top: 0, left: 0, width: 10, height: 20 } }, + ]; + + const visibleTooltip = computeRectAnnotationTooltipState(cursorPosition, annotationRects, 0, chartDimensions); + const expectedVisibleTooltipState: AnnotationTooltipState = { + isVisible: true, + annotationType: AnnotationTypes.Rectangle, + anchor: { + top: cursorPosition.y, + left: cursorPosition.x, + }, + }; + + expect(visibleTooltip).toEqual(expectedVisibleTooltipState); + }); +}); diff --git a/src/chart_types/xy_chart/annotations/rect/tooltip.ts b/src/chart_types/xy_chart/annotations/rect/tooltip.ts index 4a978f0634..cb8d3defbe 100644 --- a/src/chart_types/xy_chart/annotations/rect/tooltip.ts +++ b/src/chart_types/xy_chart/annotations/rect/tooltip.ts @@ -17,12 +17,13 @@ * under the License. */ +import { Rect } from '../../../../geoms/types'; import { Rotation } from '../../../../utils/commons'; import { Dimensions } from '../../../../utils/dimensions'; import { Point } from '../../../../utils/point'; +import { isHorizontalRotation } from '../../state/utils/common'; import { AnnotationTypes } from '../../utils/specs'; import { AnnotationTooltipState, Bounds } from '../types'; -import { getTransformedCursor } from '../utils'; import { isWithinRectBounds } from './dimensions'; import { AnnotationRectProps } from './types'; @@ -30,20 +31,23 @@ import { AnnotationRectProps } from './types'; export function computeRectAnnotationTooltipState( cursorPosition: Point, annotationRects: AnnotationRectProps[], - chartRotation: Rotation, + rotation: Rotation, chartDimensions: Dimensions, ): AnnotationTooltipState | null { - const rotatedProjectedCursorPosition = getTransformedCursor(cursorPosition, chartDimensions, chartRotation, true); const totalAnnotationRect = annotationRects.length; + for (let i = 0; i < totalAnnotationRect; i++) { const rectProps = annotationRects[i]; - const { rect, details } = rectProps; - const startX = rect.x; + const { details, panel } = rectProps; + + const rect = transformRotateRect(rectProps.rect, rotation, panel); + + const startX = rect.x + chartDimensions.left + panel.left; const endX = startX + rect.width; - const startY = rect.y; + const startY = rect.y + chartDimensions.top + panel.top; const endY = startY + rect.height; const bounds: Bounds = { startX, endX, startY, endY }; - const isWithinBounds = isWithinRectBounds(rotatedProjectedCursorPosition, bounds); + const isWithinBounds = isWithinRectBounds(cursorPosition, bounds); if (isWithinBounds) { return { isVisible: true, @@ -59,3 +63,36 @@ export function computeRectAnnotationTooltipState( return null; } + +function transformRotateRect(rect: Rect, rotation: Rotation, dim: Dimensions): Rect { + const isHorizontalRotated = isHorizontalRotation(rotation); + const width = isHorizontalRotated ? dim.width : dim.height; + const height = isHorizontalRotated ? dim.height : dim.width; + + switch (rotation) { + case 90: + return { + x: height - rect.height - rect.y, + y: rect.x, + width: rect.height, + height: rect.width, + }; + case -90: + return { + x: rect.y, + y: width - rect.x - rect.width, + width: rect.height, + height: rect.width, + }; + case 180: + return { + x: width - rect.x - rect.width, + y: height - rect.y - rect.height, + width: rect.width, + height: rect.height, + }; + case 0: + default: + return rect; + } +} diff --git a/src/chart_types/xy_chart/annotations/rect/types.ts b/src/chart_types/xy_chart/annotations/rect/types.ts index de9638df57..0102654f6a 100644 --- a/src/chart_types/xy_chart/annotations/rect/types.ts +++ b/src/chart_types/xy_chart/annotations/rect/types.ts @@ -16,6 +16,8 @@ * specific language governing permissions and limitations * under the License. */ +import { Dimensions } from '../../../../utils/dimensions'; + export interface AnnotationRectProps { rect: { x: number; @@ -23,5 +25,6 @@ export interface AnnotationRectProps { width: number; height: number; }; + panel: Dimensions; details?: string; } diff --git a/src/chart_types/xy_chart/annotations/tooltip.ts b/src/chart_types/xy_chart/annotations/tooltip.ts index 87f04c3b1e..c5e9c32edb 100644 --- a/src/chart_types/xy_chart/annotations/tooltip.ts +++ b/src/chart_types/xy_chart/annotations/tooltip.ts @@ -39,12 +39,13 @@ export function computeAnnotationTooltipState( chartDimensions: Dimensions, ): AnnotationTooltipState | null { // allow picking up the last spec added as the top most or use it's zIndex value - const sortedSpecs = annotationSpecs + const sortedAnnotationSpecs = annotationSpecs .slice() .reverse() .sort(({ zIndex: a = Number.MIN_SAFE_INTEGER }, { zIndex: b = Number.MIN_SAFE_INTEGER }) => b - a); - // eslint-disable-next-line no-restricted-syntax - for (const spec of sortedSpecs) { + + for (let i = 0; i < sortedAnnotationSpecs.length; i++) { + const spec = sortedAnnotationSpecs[i]; const annotationDimension = annotationDimensions.get(spec.id); if (spec.hideTooltips || !annotationDimension) { continue; diff --git a/src/chart_types/xy_chart/annotations/utils.test.ts b/src/chart_types/xy_chart/annotations/utils.test.ts index aedaf5814c..007292c29f 100644 --- a/src/chart_types/xy_chart/annotations/utils.test.ts +++ b/src/chart_types/xy_chart/annotations/utils.test.ts @@ -17,1124 +17,31 @@ * under the License. */ -import { RecursivePartial } from '@elastic/eui'; -import React from 'react'; - -import { ChartTypes } from '../..'; -import { MockGlobalSpec, MockSeriesSpec, MockAnnotationSpec } from '../../../mocks/specs'; -import { MockStore } from '../../../mocks/store'; -import { Scale, ScaleBand, ScaleContinuous } from '../../../scales'; -import { ScaleType } from '../../../scales/constants'; -import { SpecTypes } from '../../../specs/constants'; +import { MockGlobalSpec } from '../../../mocks/specs'; import { Position, Rotation } from '../../../utils/commons'; import { Dimensions } from '../../../utils/dimensions'; -import { GroupId, AnnotationId } from '../../../utils/ids'; -import { Point } from '../../../utils/point'; -import { DEFAULT_ANNOTATION_LINE_STYLE, AxisStyle } from '../../../utils/themes/theme'; -import { computeAnnotationDimensionsSelector } from '../state/selectors/compute_annotations'; -import { - AnnotationDomainTypes, - AnnotationSpec, - AxisSpec, - LineAnnotationSpec, - RectAnnotationSpec, - AnnotationTypes, -} from '../utils/specs'; -import { computeLineAnnotationDimensions } from './line/dimensions'; -import { computeLineAnnotationTooltipState } from './line/tooltip'; -import { AnnotationLineProps } from './line/types'; -import { computeRectAnnotationDimensions, isWithinRectBounds } from './rect/dimensions'; -import { computeRectAnnotationTooltipState } from './rect/tooltip'; -import { computeAnnotationTooltipState } from './tooltip'; -import { AnnotationDimensions, AnnotationTooltipState, Bounds } from './types'; -import { computeAnnotationDimensions, getAnnotationAxis, getTransformedCursor, invertTranformedCursor } from './utils'; - -describe('annotation utils', () => { - const minRange = 0; - const maxRange = 100; - - const continuousData = [0, 10]; - const continuousScale = new ScaleContinuous( - { - type: ScaleType.Linear, - domain: continuousData, - range: [minRange, maxRange], - }, - { bandwidth: 10, minInterval: 1 }, - ); - - const ordinalData = ['a', 'b', 'c', 'd', 'a', 'b', 'c']; - const ordinalScale = new ScaleBand(ordinalData, [minRange, maxRange]); - - const chartDimensions: Dimensions = { - width: 10, - height: 20, - top: 5, - left: 15, - }; +import { AnnotationDomainTypes } from '../utils/specs'; +import { getAnnotationAxis, getTransformedCursor, invertTranformedCursor } from './utils'; +describe('Annotation utils', () => { const groupId = 'foo-group'; - const style: RecursivePartial = { - tickLine: { - size: 10, - padding: 10, - }, - }; - const axesSpecs: AxisSpec[] = []; - const verticalAxisSpec: AxisSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Axis, + const verticalAxisSpec = MockGlobalSpec.axis({ id: 'vertical_axis', groupId, - hide: false, - showOverlappingTicks: false, - showOverlappingLabels: false, position: Position.Left, - style, - tickFormat: (value: any) => value.toString(), - showGridLines: true, - }; - const horizontalAxisSpec: AxisSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Axis, - id: 'horizontal_axis', + }); + const horizontalAxisSpec = MockGlobalSpec.axis({ + id: 'vertical_axis', groupId, - hide: false, - showOverlappingTicks: false, - showOverlappingLabels: false, position: Position.Bottom, - style, - tickFormat: (value: any) => value.toString(), - showGridLines: true, - }; - - axesSpecs.push(verticalAxisSpec); - - test('should compute rect annotation in x ordinal scale', () => { - const store = MockStore.default(); - const settings = MockGlobalSpec.settingsNoMargins(); - const spec = MockSeriesSpec.bar({ - xScaleType: ScaleType.Ordinal, - groupId, - data: [ - { x: 'a', y: 1 }, - { x: 'b', y: 0 }, - { x: 'c', y: 10 }, - { x: 'd', y: 5 }, - ], - }); - - const lineAnnotation = MockAnnotationSpec.line({ - id: 'foo', - groupId, - domainType: AnnotationDomainTypes.YDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - }); - - const rectAnnotation = MockAnnotationSpec.rect({ - id: 'rect', - groupId, - dataValues: [{ coordinates: { x0: 'a', x1: 'b', y0: 3, y1: 5 } }], - }); - - MockStore.addSpecs([settings, spec, lineAnnotation, rectAnnotation], store); - const dimensions = computeAnnotationDimensionsSelector(store.getState()); - - const expectedDimensions = new Map(); - expectedDimensions.set('foo', [ - { - linePathPoints: { - start: { x1: 0, y1: 80 }, - end: { x2: 100, y2: 80 }, - }, - marker: undefined, - details: { detailsText: 'foo', headerText: '2' }, - }, - ]); - expectedDimensions.set('rect', [{ details: undefined, rect: { x: 0, y: 50, width: 50, height: 20 } }]); - - expect(dimensions).toEqual(expectedDimensions); - }); - - test('should compute annotation dimensions also with missing axis', () => { - const chartRotation: Rotation = 0; - const yScales: Map = new Map(); - yScales.set(groupId, continuousScale); - - const xScale: Scale = ordinalScale; - - const annotations: AnnotationSpec[] = []; - const id = 'foo'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id, - domainType: AnnotationDomainTypes.YDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - annotations.push(lineAnnotation); - - const dimensions = computeAnnotationDimensions( - annotations, - chartDimensions, - chartRotation, - yScales, - xScale, - [], // empty axesSpecs - false, - ); - expect(dimensions.size).toEqual(1); - }); - - test('should compute line annotation dimensions for yDomain on a yScale (chartRotation 0, left axis)', () => { - const chartRotation: Rotation = 0; - const yScales: Map = new Map(); - yScales.set(groupId, continuousScale); - - const xScale: Scale = ordinalScale; - - const id = 'foo-line'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id, - domainType: AnnotationDomainTypes.YDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const dimensions = computeLineAnnotationDimensions( - lineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Left, - ); - const expectedDimensions: AnnotationLineProps[] = [ - { - linePathPoints: { - start: { x1: 0, y1: 20 }, - end: { x2: 10, y2: 20 }, - }, - details: { detailsText: 'foo', headerText: '2' }, - }, - ]; - expect(dimensions).toEqual(expectedDimensions); - }); - - test('should compute line annotation dimensions for yDomain on a yScale (chartRotation 0, right axis)', () => { - const chartRotation: Rotation = 0; - const yScales: Map = new Map(); - yScales.set(groupId, continuousScale); - - const xScale: Scale = ordinalScale; - - const annotationId = 'foo-line'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.YDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const dimensions = computeLineAnnotationDimensions( - lineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Right, - ); - const expectedDimensions: AnnotationLineProps[] = [ - { - linePathPoints: { - start: { x1: 0, y1: 20 }, - end: { x2: 10, y2: 20 }, - }, - details: { detailsText: 'foo', headerText: '2' }, - }, - ]; - expect(dimensions).toEqual(expectedDimensions); - }); - - test('should compute line annotation dimensions for yDomain on a yScale (chartRotation 90)', () => { - const chartRotation: Rotation = 90; - const yScales: Map = new Map(); - yScales.set(groupId, continuousScale); - - const xScale: Scale = ordinalScale; - - const annotationId = 'foo-line'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.YDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const dimensions = computeLineAnnotationDimensions( - lineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Left, - ); - const expectedDimensions: AnnotationLineProps[] = [ - { - linePathPoints: { - start: { x1: 0, y1: 20 }, - end: { x2: 20, y2: 20 }, - }, - details: { detailsText: 'foo', headerText: '2' }, - marker: undefined, - }, - ]; - expect(dimensions).toEqual(expectedDimensions); - }); - - test('should not compute line annotation dimensions for yDomain if no corresponding yScale', () => { - const chartRotation: Rotation = 0; - const yScales: Map = new Map(); - const xScale: Scale = ordinalScale; - - const annotationId = 'foo-line'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.YDomain, - dataValues: [], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const dimensions = computeLineAnnotationDimensions( - lineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Left, - ); - expect(dimensions).toEqual(null); - }); - - test('should compute line annotation dimensions for xDomain (chartRotation 0, ordinal scale)', () => { - const chartRotation: Rotation = 0; - const yScales: Map = new Map(); - const xScale: Scale = ordinalScale; - - const annotationId = 'foo-line'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.XDomain, - dataValues: [{ dataValue: 'a', details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const dimensions = computeLineAnnotationDimensions( - lineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Left, - ); - const expectedDimensions: AnnotationLineProps[] = [ - { - linePathPoints: { - start: { x1: 12.5, y1: 0 }, - end: { x2: 12.5, y2: 20 }, - }, - details: { detailsText: 'foo', headerText: 'a' }, - }, - ]; - expect(dimensions).toEqual(expectedDimensions); - }); - - test('should compute line annotation dimensions for xDomain (chartRotation 0, continuous scale, top axis)', () => { - const chartRotation: Rotation = 0; - const yScales: Map = new Map(); - const xScale: Scale = continuousScale; - - const annotationId = 'foo-line'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.XDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const dimensions = computeLineAnnotationDimensions( - lineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Top, - ); - const expectedDimensions: AnnotationLineProps[] = [ - { - linePathPoints: { - start: { x1: 25, y1: 0 }, - end: { x2: 25, y2: 20 }, - }, - details: { detailsText: 'foo', headerText: '2' }, - marker: undefined, - }, - ]; - expect(dimensions).toEqual(expectedDimensions); - }); - - test('should compute line annotation dimensions for xDomain (chartRotation 0, continuous scale, bottom axis)', () => { - const chartRotation: Rotation = 0; - const yScales: Map = new Map(); - const xScale: Scale = continuousScale; - - const annotationId = 'foo-line'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.XDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const dimensions = computeLineAnnotationDimensions( - lineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Bottom, - ); - const expectedDimensions: AnnotationLineProps[] = [ - { - linePathPoints: { - start: { x1: 25, y1: 0 }, - end: { x2: 25, y2: 20 }, - }, - details: { detailsText: 'foo', headerText: '2' }, - marker: undefined, - }, - ]; - expect(dimensions).toEqual(expectedDimensions); - }); - - test('should compute line annotation dimensions for xDomain on a xScale (chartRotation 90, ordinal scale)', () => { - const chartRotation: Rotation = 90; - const yScales: Map = new Map(); - - const xScale: Scale = ordinalScale; - - const annotationId = 'foo-line'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.XDomain, - dataValues: [{ dataValue: 'a', details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const dimensions = computeLineAnnotationDimensions( - lineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Left, - ); - const expectedDimensions: AnnotationLineProps[] = [ - { - linePathPoints: { - start: { x1: 12.5, y1: 0 }, - end: { x2: 12.5, y2: 10 }, - }, - details: { detailsText: 'foo', headerText: 'a' }, - marker: undefined, - }, - ]; - expect(dimensions).toEqual(expectedDimensions); - }); - - test('should compute line annotation dimensions for xDomain on a xScale (chartRotation 90, continuous scale)', () => { - const chartRotation: Rotation = 90; - const yScales: Map = new Map(); - - const xScale: Scale = continuousScale; - - const annotationId = 'foo-line'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.XDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const dimensions = computeLineAnnotationDimensions( - lineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Left, - ); - const expectedDimensions: AnnotationLineProps[] = [ - { - linePathPoints: { - start: { x1: 25, y1: 0 }, - end: { x2: 25, y2: 10 }, - }, - details: { detailsText: 'foo', headerText: '2' }, - marker: undefined, - }, - ]; - expect(dimensions).toEqual(expectedDimensions); - }); - - test('should compute line annotation dimensions for xDomain on a xScale (chartRotation -90, continuous scale)', () => { - const chartRotation: Rotation = -90; - const yScales: Map = new Map(); - - const xScale: Scale = continuousScale; - - const annotationId = 'foo-line'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.XDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const dimensions = computeLineAnnotationDimensions( - lineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Left, - ); - const expectedDimensions: AnnotationLineProps[] = [ - { - linePathPoints: { - start: { x1: 25, y1: 0 }, - end: { x2: 25, y2: 10 }, - }, - details: { detailsText: 'foo', headerText: '2' }, - marker: undefined, - }, - ]; - expect(dimensions).toEqual(expectedDimensions); - }); - - test('should compute line annotation dimensions for xDomain (chartRotation 180, continuous scale, top axis)', () => { - const chartRotation: Rotation = 180; - const yScales: Map = new Map(); - - const xScale: Scale = continuousScale; - - const annotationId = 'foo-line'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.XDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const dimensions = computeLineAnnotationDimensions( - lineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Top, - ); - const expectedDimensions: AnnotationLineProps[] = [ - { - linePathPoints: { - start: { x1: 25, y1: 0 }, - end: { x2: 25, y2: 20 }, - }, - details: { detailsText: 'foo', headerText: '2' }, - marker: undefined, - }, - ]; - expect(dimensions).toEqual(expectedDimensions); - }); - - test('should compute line annotation dimensions for xDomain (chartRotation 180, continuous scale, bottom axis)', () => { - const chartRotation: Rotation = 180; - const yScales: Map = new Map(); - const xScale: Scale = continuousScale; - - const annotationId = 'foo-line'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.XDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const dimensions = computeLineAnnotationDimensions( - lineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Bottom, - ); - const expectedDimensions: AnnotationLineProps[] = [ - { - details: { detailsText: 'foo', headerText: '2' }, - linePathPoints: { - start: { x1: 25, y1: 0 }, - end: { x2: 25, y2: 20 }, - }, - marker: undefined, - }, - ]; - expect(dimensions).toEqual(expectedDimensions); - }); - - test('should not compute annotation line values for invalid data values or AnnotationSpec.hideLines', () => { - const chartRotation: Rotation = 0; - const yScales: Map = new Map(); - yScales.set(groupId, continuousScale); - - const xScale: Scale = ordinalScale; - - const annotationId = 'foo-line'; - const invalidXLineAnnotation: AnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.XDomain, - dataValues: [{ dataValue: 'e', details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const emptyXDimensions = computeLineAnnotationDimensions( - invalidXLineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Right, - ); - - expect(emptyXDimensions).toEqual([]); - - const invalidStringXLineAnnotation: AnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.XDomain, - dataValues: [{ dataValue: '', details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const invalidStringXDimensions = computeLineAnnotationDimensions( - invalidStringXLineAnnotation, - chartDimensions, - chartRotation, - yScales, - continuousScale, - false, - Position.Right, - ); - - expect(invalidStringXDimensions).toEqual([]); - - const outOfBoundsXLineAnnotation: AnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.XDomain, - dataValues: [{ dataValue: -999, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const emptyOutOfBoundsXDimensions = computeLineAnnotationDimensions( - outOfBoundsXLineAnnotation, - chartDimensions, - chartRotation, - yScales, - continuousScale, - false, - Position.Right, - ); - expect(emptyOutOfBoundsXDimensions).toHaveLength(0); - - const invalidYLineAnnotation: AnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.YDomain, - dataValues: [{ dataValue: 'e', details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const emptyOutOfBoundsYDimensions = computeLineAnnotationDimensions( - invalidYLineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Right, - ); - - expect(emptyOutOfBoundsYDimensions).toHaveLength(0); - - const outOfBoundsYLineAnnotation: AnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.YDomain, - dataValues: [{ dataValue: -999, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const outOfBoundsYAnn = computeLineAnnotationDimensions( - outOfBoundsYLineAnnotation, - chartDimensions, - chartRotation, - yScales, - xScale, - false, - Position.Right, - ); - - expect(outOfBoundsYAnn).toHaveLength(0); - - const invalidStringYLineAnnotation: AnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.YDomain, - dataValues: [{ dataValue: '', details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const invalidStringYDimensions = computeLineAnnotationDimensions( - invalidStringYLineAnnotation, - chartDimensions, - chartRotation, - yScales, - continuousScale, - false, - Position.Right, - ); - - expect(invalidStringYDimensions).toEqual([]); - - const validHiddenAnnotation: AnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.XDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - hideLines: true, - }; - - const hiddenAnnotationDimensions = computeLineAnnotationDimensions( - validHiddenAnnotation, - chartDimensions, - chartRotation, - yScales, - continuousScale, - false, - Position.Right, - ); - - expect(hiddenAnnotationDimensions).toEqual(null); - }); - - test('should compute the tooltip state for an annotation line', () => { - const cursorPosition: Point = { x: 16, y: 7 }; - const annotationLines: AnnotationLineProps[] = [ - { - linePathPoints: { - start: { x1: 1, y1: 2 }, - end: { x2: 3, y2: 4 }, - }, - details: {}, - marker: { - icon: React.createElement('div'), - color: 'red', - dimension: { width: 10, height: 10 }, - position: { top: 0, left: 0 }, - }, - }, - { - linePathPoints: { - start: { x1: 0, y1: 10 }, - end: { x2: 20, y2: 10 }, - }, - details: {}, - marker: { - icon: React.createElement('div'), - color: 'red', - dimension: { width: 20, height: 20 }, - position: { top: 0, left: 0 }, - }, - }, - ]; - - const localAxesSpecs: AxisSpec[] = []; - - // missing annotation axis (xDomain) - const missingTooltipState = computeLineAnnotationTooltipState( - cursorPosition, - annotationLines, - groupId, - AnnotationDomainTypes.XDomain, - localAxesSpecs, - chartDimensions, - ); - - expect(missingTooltipState).toBeNull(); - - // add axis for xDomain annotation - localAxesSpecs.push(horizontalAxisSpec); - - const xDomainTooltipState = computeLineAnnotationTooltipState( - cursorPosition, - annotationLines, - groupId, - AnnotationDomainTypes.XDomain, - localAxesSpecs, - chartDimensions, - ); - const expectedXDomainTooltipState = { - isVisible: true, - annotationType: AnnotationTypes.Line, - anchor: { - height: 10, - left: 15, - top: 5, - width: 10, - }, - }; - expect(xDomainTooltipState).toMatchObject(expectedXDomainTooltipState); - - // rotated xDomain - const xDomainRotatedTooltipState = computeLineAnnotationTooltipState( - { x: 24, y: 23 }, - annotationLines, - groupId, - AnnotationDomainTypes.XDomain, - localAxesSpecs, - chartDimensions, - ); - const expectedXDomainRotatedTooltipState: AnnotationTooltipState = { - isVisible: true, - anchor: { - left: 15, - top: 5, - }, - annotationType: AnnotationTypes.Line, - }; - - expect(xDomainRotatedTooltipState).toMatchObject(expectedXDomainRotatedTooltipState); - - // add axis for yDomain annotation - localAxesSpecs.push(verticalAxisSpec); - - const yDomainTooltipState = computeLineAnnotationTooltipState( - cursorPosition, - annotationLines, - groupId, - AnnotationDomainTypes.YDomain, - localAxesSpecs, - chartDimensions, - ); - const expectedYDomainTooltipState: AnnotationTooltipState = { - isVisible: true, - anchor: { - left: 15, - top: 5, - }, - annotationType: AnnotationTypes.Line, - }; - - expect(yDomainTooltipState).toMatchObject(expectedYDomainTooltipState); - - const flippedYDomainTooltipState = computeLineAnnotationTooltipState( - { x: 24, y: 23 }, - annotationLines, - groupId, - AnnotationDomainTypes.YDomain, - localAxesSpecs, - chartDimensions, - ); - const expectedFlippedYDomainTooltipState: AnnotationTooltipState = { - isVisible: true, - anchor: { - left: 15, - top: 5, - }, - annotationType: AnnotationTypes.Line, - }; - - expect(flippedYDomainTooltipState).toMatchObject(expectedFlippedYDomainTooltipState); - - const rotatedYDomainTooltipState = computeLineAnnotationTooltipState( - { x: 25, y: 15 }, - annotationLines, - groupId, - AnnotationDomainTypes.YDomain, - localAxesSpecs, - chartDimensions, - ); - const expectedRotatedYDomainTooltipState: AnnotationTooltipState = { - isVisible: true, - anchor: { - left: 15, - top: 5, - }, - annotationType: AnnotationTypes.Line, - }; - - expect(rotatedYDomainTooltipState).toMatchObject(expectedRotatedYDomainTooltipState); - }); - - test('should compute the tooltip state for an annotation', () => { - const annotations: AnnotationSpec[] = []; - const annotationId = 'foo'; - const lineAnnotation: LineAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - annotationType: AnnotationTypes.Line, - id: annotationId, - domainType: AnnotationDomainTypes.YDomain, - dataValues: [{ dataValue: 2, details: 'foo' }], - groupId, - style: DEFAULT_ANNOTATION_LINE_STYLE, - }; - - const cursorPosition: Point = { x: 16, y: 7 }; - - const annotationLines: AnnotationLineProps[] = [ - { - linePathPoints: { start: { x1: 1, y1: 2 }, end: { x2: 3, y2: 4 } }, - details: {}, - marker: { - icon: React.createElement('div'), - color: 'red', - dimension: { width: 10, height: 10 }, - position: { top: 0, left: 0 }, - }, - }, - ]; - const chartRotation: Rotation = 0; - const localAxesSpecs: AxisSpec[] = []; - - const annotationDimensions = new Map(); - annotationDimensions.set(annotationId, annotationLines); - - // missing annotations - const missingSpecTooltipState = computeAnnotationTooltipState( - cursorPosition, - annotationDimensions, - annotations, - chartRotation, - localAxesSpecs, - chartDimensions, - ); - - expect(missingSpecTooltipState).toBe(null); - - // add valid annotation axis - annotations.push(lineAnnotation); - localAxesSpecs.push(verticalAxisSpec); - - // hide tooltipState - lineAnnotation.hideTooltips = true; - - const hideTooltipState = computeAnnotationTooltipState( - cursorPosition, - annotationDimensions, - annotations, - chartRotation, - localAxesSpecs, - chartDimensions, - ); - - expect(hideTooltipState).toBe(null); - - // show tooltipState, hide lines - lineAnnotation.hideTooltips = false; - lineAnnotation.hideLines = true; - - const hideLinesTooltipState = computeAnnotationTooltipState( - cursorPosition, - annotationDimensions, - annotations, - chartRotation, - localAxesSpecs, - chartDimensions, - ); - - expect(hideLinesTooltipState).toBe(null); - - // show tooltipState & lines - lineAnnotation.hideTooltips = false; - lineAnnotation.hideLines = false; - - const tooltipState = computeAnnotationTooltipState( - cursorPosition, - annotationDimensions, - annotations, - chartRotation, - localAxesSpecs, - chartDimensions, - ); - - const expectedTooltipState = { - isVisible: true, - annotationType: AnnotationTypes.Line, - anchor: { - height: 10, - left: 15, - top: 5, - width: 10, - }, - }; - - expect(tooltipState).toMatchObject(expectedTooltipState); - - // rect annotation tooltip - const annotationRectangle: RectAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - id: 'rect', - groupId, - annotationType: AnnotationTypes.Rectangle, - dataValues: [{ coordinates: { x0: 1, x1: 2, y0: 3, y1: 5 } }], - }; - - const rectAnnotations: RectAnnotationSpec[] = []; - rectAnnotations.push(annotationRectangle); - - const rectAnnotationDimensions = [{ rect: { x: 2, y: 3, width: 3, height: 5 } }]; - annotationDimensions.set(annotationRectangle.id, rectAnnotationDimensions); - - const rectTooltipState = computeAnnotationTooltipState( - { x: 18, y: 9 }, - annotationDimensions, - rectAnnotations, - chartRotation, - localAxesSpecs, - chartDimensions, - ); - - expect(rectTooltipState).toMatchObject({ - isVisible: true, - annotationType: AnnotationTypes.Rectangle, - anchor: { - left: 18, - top: 9, - }, - }); - annotationRectangle.hideTooltips = true; - - const rectHideTooltipState = computeAnnotationTooltipState( - { x: 3, y: 4 }, - annotationDimensions, - rectAnnotations, - chartRotation, - localAxesSpecs, - chartDimensions, - ); - - expect(rectHideTooltipState).toBe(null); }); test('should get associated axis for an annotation', () => { - const localAxesSpecs: AxisSpec[] = []; - - const noAxis = getAnnotationAxis(localAxesSpecs, groupId, AnnotationDomainTypes.XDomain, 0); + const noAxis = getAnnotationAxis([], groupId, AnnotationDomainTypes.XDomain, 0); expect(noAxis).toBeUndefined(); - localAxesSpecs.push(horizontalAxisSpec); - localAxesSpecs.push(verticalAxisSpec); + const localAxesSpecs = [horizontalAxisSpec, verticalAxisSpec]; const xAnnotationAxisPosition = getAnnotationAxis(localAxesSpecs, groupId, AnnotationDomainTypes.XDomain, 0); expect(xAnnotationAxisPosition).toEqual(Position.Bottom); @@ -1142,161 +49,15 @@ describe('annotation utils', () => { const yAnnotationAxisPosition = getAnnotationAxis(localAxesSpecs, groupId, AnnotationDomainTypes.YDomain, 0); expect(yAnnotationAxisPosition).toEqual(Position.Left); }); - test('should not compute rectangle annotation dimensions when no yScale', () => { - const yScales: Map = new Map(); - yScales.set(groupId, continuousScale); - - const xScale: Scale = continuousScale; - - const annotationRectangle: RectAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - id: 'rect', - groupId: 'foo', - annotationType: AnnotationTypes.Rectangle, - dataValues: [{ coordinates: { x0: 1, x1: 2, y0: 3, y1: 5 } }], - }; - - const noYScale = computeRectAnnotationDimensions(annotationRectangle, chartDimensions, yScales, xScale); - - expect(noYScale).toEqual([]); - }); - test('should skip computing rectangle annotation dimensions when annotation data invalid', () => { - const yScales: Map = new Map(); - yScales.set(groupId, continuousScale); - - const xScale: Scale = continuousScale; - - const annotationRectangle: RectAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - id: 'rect', - groupId, - annotationType: AnnotationTypes.Rectangle, - dataValues: [ - { coordinates: { x0: 1, x1: 2, y0: -10, y1: 5 } }, - { coordinates: { x0: null, x1: null, y0: null, y1: null } }, - ], - }; - - const skippedInvalid = computeRectAnnotationDimensions(annotationRectangle, chartDimensions, yScales, xScale); - - expect(skippedInvalid).toHaveLength(1); - }); - test('should compute rectangle dimensions shifted for histogram mode', () => { - const yScales: Map = new Map(); - yScales.set( - groupId, - new ScaleContinuous( - { - type: ScaleType.Linear, - domain: continuousData, - range: [minRange, maxRange], - }, - { bandwidth: 0, minInterval: 1 }, - ), - ); - - const xScale: Scale = new ScaleContinuous( - { type: ScaleType.Linear, domain: continuousData, range: [minRange, maxRange] }, - { bandwidth: 72, minInterval: 1 }, - ); - - const annotationRectangle: RectAnnotationSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Annotation, - id: 'rect', - groupId, - annotationType: AnnotationTypes.Rectangle, - dataValues: [ - { coordinates: { x0: 1, x1: null, y0: null, y1: null } }, - { coordinates: { x0: null, x1: 1, y0: null, y1: null } }, - { coordinates: { x0: null, x1: null, y0: 1, y1: null } }, - { coordinates: { x0: null, x1: null, y0: null, y1: 1 } }, - ], - }; - - const dimensions = computeRectAnnotationDimensions(annotationRectangle, chartDimensions, yScales, xScale); - - const [dims1, dims2, dims3, dims4] = dimensions; - expect(dims1.rect.x).toBe(10); - expect(dims1.rect.y).toBe(100); - expect(dims1.rect.height).toBe(100); - expect(dims1.rect.width).toBeCloseTo(100); - - expect(dims2.rect.x).toBe(0); - expect(dims2.rect.y).toBe(100); - expect(dims2.rect.width).toBe(20); - expect(dims2.rect.height).toBe(100); - - expect(dims3.rect.x).toBe(0); - expect(dims3.rect.y).toBe(100); - expect(dims3.rect.width).toBeCloseTo(110); - expect(dims3.rect.height).toBe(90); - - expect(dims4.rect.x).toBe(0); - expect(dims4.rect.y).toBe(10); - expect(dims4.rect.width).toBeCloseTo(110); - expect(dims4.rect.height).toBe(10); - }); - - test('should determine if a point is within a rectangle annotation', () => { - const cursorPosition = { x: 3, y: 4 }; - - const outOfXBounds: Bounds = { startX: 4, endX: 5, startY: 3, endY: 5 }; - const outOfYBounds: Bounds = { startX: 2, endX: 4, startY: 5, endY: 6 }; - const withinBounds: Bounds = { startX: 2, endX: 4, startY: 3, endY: 5 }; - const withinBoundsReverseXScale: Bounds = { startX: 4, endX: 2, startY: 3, endY: 5 }; - const withinBoundsReverseYScale: Bounds = { startX: 2, endX: 4, startY: 5, endY: 3 }; - - // chart rotation 0 - expect(isWithinRectBounds(cursorPosition, outOfXBounds)).toBe(false); - expect(isWithinRectBounds(cursorPosition, outOfYBounds)).toBe(false); - expect(isWithinRectBounds(cursorPosition, withinBounds)).toBe(true); - expect(isWithinRectBounds(cursorPosition, withinBoundsReverseXScale)).toBe(false); - expect(isWithinRectBounds(cursorPosition, withinBoundsReverseYScale)).toBe(false); - - // chart rotation 180 - expect(isWithinRectBounds(cursorPosition, outOfXBounds)).toBe(false); - expect(isWithinRectBounds(cursorPosition, outOfYBounds)).toBe(false); - expect(isWithinRectBounds(cursorPosition, withinBounds)).toBe(true); - expect(isWithinRectBounds(cursorPosition, withinBoundsReverseXScale)).toBe(false); - expect(isWithinRectBounds(cursorPosition, withinBoundsReverseYScale)).toBe(false); - - // chart rotation 90 - expect(isWithinRectBounds(cursorPosition, outOfXBounds)).toBe(false); - expect(isWithinRectBounds(cursorPosition, outOfYBounds)).toBe(false); - expect(isWithinRectBounds(cursorPosition, withinBounds)).toBe(true); - expect(isWithinRectBounds(cursorPosition, withinBoundsReverseXScale)).toBe(false); - expect(isWithinRectBounds(cursorPosition, withinBoundsReverseYScale)).toBe(false); - - // chart rotation -90 - expect(isWithinRectBounds(cursorPosition, outOfXBounds)).toBe(false); - expect(isWithinRectBounds(cursorPosition, outOfYBounds)).toBe(false); - expect(isWithinRectBounds(cursorPosition, withinBounds)).toBe(true); - expect(isWithinRectBounds(cursorPosition, withinBoundsReverseXScale)).toBe(false); - expect(isWithinRectBounds(cursorPosition, withinBoundsReverseYScale)).toBe(false); - }); - test('should compute tooltip state for rect annotation', () => { - const cursorPosition = { x: 18, y: 9 }; - const annotationRects = [{ rect: { x: 2, y: 3, width: 3, height: 5 } }]; - - const visibleTooltip = computeRectAnnotationTooltipState(cursorPosition, annotationRects, 0, chartDimensions); - const expectedVisibleTooltipState: AnnotationTooltipState = { - isVisible: true, - annotationType: AnnotationTypes.Rectangle, - anchor: { - top: cursorPosition.y, - left: cursorPosition.x, - }, - }; - - expect(visibleTooltip).toEqual(expectedVisibleTooltipState); - }); test('should get rotated cursor position', () => { const cursorPosition = { x: 1, y: 2 }; - + const chartDimensions: Dimensions = { + width: 10, + height: 20, + top: 5, + left: 15, + }; expect(getTransformedCursor(cursorPosition, chartDimensions, 0)).toEqual(cursorPosition); expect(getTransformedCursor(cursorPosition, chartDimensions, 90)).toEqual({ x: 2, y: 9 }); expect(getTransformedCursor(cursorPosition, chartDimensions, -90)).toEqual({ x: 18, y: 1 }); @@ -1305,7 +66,12 @@ describe('annotation utils', () => { describe('#invertTranformedCursor', () => { const cursorPosition = { x: 1, y: 2 }; - + const chartDimensions: Dimensions = { + width: 10, + height: 20, + top: 5, + left: 15, + }; it.each([0, 90, -90, 180])('Should invert rotated cursor - rotation %d', (rotation) => { expect( invertTranformedCursor( diff --git a/src/chart_types/xy_chart/annotations/utils.ts b/src/chart_types/xy_chart/annotations/utils.ts index a495eee154..26d0396aa9 100644 --- a/src/chart_types/xy_chart/annotations/utils.ts +++ b/src/chart_types/xy_chart/annotations/utils.ts @@ -22,6 +22,7 @@ import { Rotation, Position } from '../../../utils/commons'; import { Dimensions } from '../../../utils/dimensions'; import { AnnotationId, GroupId } from '../../../utils/ids'; import { Point } from '../../../utils/point'; +import { SmallMultipleScales } from '../state/selectors/compute_small_multiple_scales'; import { isHorizontalRotation } from '../state/utils/common'; import { getAxesSpecForSpecId } from '../state/utils/spec'; import { @@ -30,7 +31,6 @@ import { AnnotationSpec, AxisSpec, isLineAnnotation, - isRectAnnotation, } from '../utils/specs'; import { computeLineAnnotationDimensions } from './line/dimensions'; import { computeRectAnnotationDimensions } from './rect/dimensions'; @@ -139,42 +139,41 @@ export function computeAnnotationDimensions( xScale: Scale, axesSpecs: AxisSpec[], isHistogramModeEnabled: boolean, + smallMultipleScales: SmallMultipleScales, ): Map { - const annotationDimensions = new Map(); - - annotations.forEach((annotationSpec) => { + return annotations.reduce>((annotationDimensions, annotationSpec) => { const { id } = annotationSpec; + if (isLineAnnotation(annotationSpec)) { const { groupId, domainType } = annotationSpec; const annotationAxisPosition = getAnnotationAxis(axesSpecs, groupId, domainType, chartRotation); const dimensions = computeLineAnnotationDimensions( annotationSpec, - chartDimensions, chartRotation, yScales, xScale, + smallMultipleScales, isHistogramModeEnabled, annotationAxisPosition, ); - - if (dimensions) { - annotationDimensions.set(id, dimensions); - } - } else if (isRectAnnotation(annotationSpec)) { - const dimensions = computeRectAnnotationDimensions( - annotationSpec, - chartDimensions, - yScales, - xScale, - isHistogramModeEnabled, - ); - if (dimensions) { annotationDimensions.set(id, dimensions); } + return annotationDimensions; } - }); - return annotationDimensions; + const dimensions = computeRectAnnotationDimensions( + annotationSpec, + yScales, + xScale, + smallMultipleScales, + isHistogramModeEnabled, + ); + + if (dimensions) { + annotationDimensions.set(id, dimensions); + } + return annotationDimensions; + }, new Map()); } diff --git a/src/chart_types/xy_chart/axes/axes_sizes.ts b/src/chart_types/xy_chart/axes/axes_sizes.ts new file mode 100644 index 0000000000..7953359a07 --- /dev/null +++ b/src/chart_types/xy_chart/axes/axes_sizes.ts @@ -0,0 +1,112 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Position } from '../../../utils/commons'; +import { getSimplePadding } from '../../../utils/dimensions'; +import { AxisId } from '../../../utils/ids'; +import { AxisStyle, Theme } from '../../../utils/themes/theme'; +import { getSpecsById } from '../state/utils/spec'; +import { AxisTicksDimensions, shouldShowTicks } from '../utils/axis_utils'; +import { AxisSpec } from '../utils/specs'; + +/** + * Compute the axes required size around the chart + * @param chartTheme the theme style of the chart + * @param axisDimensions the axis dimensions + * @param axesStyles a map with all the custom axis styles + * @param axisSpecs the axis specs + * @internal + */ +export function computeAxesSizes( + { axes: sharedAxesStyles, chartMargins }: Theme, + axisDimensions: Map, + axesStyles: Map, + axisSpecs: AxisSpec[], +): { left: number; right: number; top: number; bottom: number; margin: { left: number } } { + const axisMainSize = { + left: 0, + right: 0, + top: 0, + bottom: 0, + }; + const axisLabelOverflow = { + left: 0, + right: 0, + top: 0, + bottom: 0, + }; + + axisDimensions.forEach(({ maxLabelBboxWidth = 0, maxLabelBboxHeight = 0, isHidden }, id) => { + const axisSpec = getSpecsById(axisSpecs, id); + if (!axisSpec || isHidden) { + return; + } + const { tickLine, axisTitle, tickLabel } = axesStyles.get(id) ?? sharedAxesStyles; + const showTicks = shouldShowTicks(tickLine, axisSpec.hide); + const { position, title } = axisSpec; + const titlePadding = getSimplePadding(axisTitle.padding); + const labelPadding = getSimplePadding(tickLabel.padding); + const labelPaddingSum = tickLabel.visible ? labelPadding.inner + labelPadding.outer : 0; + + const tickDimension = showTicks ? tickLine.size + tickLine.padding : 0; + const titleHeight = + title !== undefined && axisTitle.visible ? axisTitle.fontSize + titlePadding.outer + titlePadding.inner : 0; + const axisDimension = labelPaddingSum + tickDimension + titleHeight; + const maxAxisHeight = tickLabel.visible ? maxLabelBboxHeight + axisDimension : axisDimension; + const maxAxisWidth = tickLabel.visible ? maxLabelBboxWidth + axisDimension : axisDimension; + + switch (position) { + case Position.Top: + axisMainSize.top += maxAxisHeight + chartMargins.top; + // find the max half label size to accommodate the left/right labels + // TODO use first and last labels + axisLabelOverflow.left = Math.max(axisLabelOverflow.left, maxLabelBboxWidth / 2); + axisLabelOverflow.right = Math.max(axisLabelOverflow.right, maxLabelBboxWidth / 2); + break; + case Position.Bottom: + axisMainSize.bottom += maxAxisHeight + chartMargins.bottom; + // find the max half label size to accommodate the left/right labels + // TODO use first and last labels + axisLabelOverflow.left = Math.max(axisLabelOverflow.left, maxLabelBboxWidth / 2); + axisLabelOverflow.right = Math.max(axisLabelOverflow.right, maxLabelBboxWidth / 2); + break; + case Position.Right: + axisMainSize.right += maxAxisWidth + chartMargins.right; + // TODO use first and last labels + axisLabelOverflow.top = Math.max(axisLabelOverflow.top, maxLabelBboxHeight / 2); + axisLabelOverflow.bottom = Math.max(axisLabelOverflow.bottom, maxLabelBboxHeight / 2); + break; + case Position.Left: + default: + axisMainSize.left += maxAxisWidth + chartMargins.left; + // TODO use first and last labels + axisLabelOverflow.top = Math.max(axisLabelOverflow.top, maxLabelBboxHeight / 2); + axisLabelOverflow.bottom = Math.max(axisLabelOverflow.bottom, maxLabelBboxHeight / 2); + } + }); + const left = Math.max(axisLabelOverflow.left + chartMargins.left, axisMainSize.left); + return { + margin: { + left: left - axisMainSize.left, + }, + left, + right: Math.max(axisLabelOverflow.right + chartMargins.right, axisMainSize.right), + top: Math.max(axisLabelOverflow.top + chartMargins.top, axisMainSize.top), + bottom: Math.max(axisLabelOverflow.bottom + chartMargins.bottom, axisMainSize.bottom), + }; +} diff --git a/src/chart_types/xy_chart/crosshair/crosshair_utils.ts b/src/chart_types/xy_chart/crosshair/crosshair_utils.ts index 4e5651b351..75aac8b180 100644 --- a/src/chart_types/xy_chart/crosshair/crosshair_utils.ts +++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.ts @@ -25,11 +25,6 @@ import { Point } from '../../../utils/point'; import { isHorizontalRotation, isVerticalRotation } from '../state/utils/common'; import { ChartDimensions } from '../utils/dimensions'; -export interface SnappedPosition { - position: number; - band: number; -} - export const DEFAULT_SNAP_POSITION_BAND = 1; /** @internal */ @@ -71,7 +66,7 @@ export function getCursorLinePosition( const { left, top, width, height } = chartDimensions; const isHorizontalRotated = isHorizontalRotation(chartRotation); if (isHorizontalRotated) { - const crosshairTop = projectedPointerPosition.y + top; + const crosshairTop = y + top; return { left, width, @@ -79,7 +74,7 @@ export function getCursorLinePosition( height: 0, }; } - const crosshairLeft = projectedPointerPosition.x + left; + const crosshairLeft = x + left; return { top, @@ -92,7 +87,7 @@ export function getCursorLinePosition( /** @internal */ export function getCursorBandPosition( chartRotation: Rotation, - chartDimensions: Dimensions, + panel: Dimensions, cursorPosition: Point, invertedValue: { value: any; @@ -102,7 +97,7 @@ export function getCursorBandPosition( xScale: Scale, totalBarsInCluster?: number, ): Dimensions & { visible: boolean } { - const { top, left, width, height } = chartDimensions; + const { top, left, width, height } = panel; const { x, y } = cursorPosition; const isHorizontalRotated = isHorizontalRotation(chartRotation); const chartWidth = isHorizontalRotated ? width : height; @@ -169,26 +164,15 @@ export function getCursorBandPosition( /** @internal */ export function getTooltipAnchorPosition( - { chartDimensions, offset }: ChartDimensions, + { offset }: ChartDimensions, chartRotation: Rotation, cursorBandPosition: Dimensions, cursorPosition: { x: number; y: number }, + panel: Dimensions, ): TooltipAnchorPosition { const isRotated = isVerticalRotation(chartRotation); - const hPosition = getHorizontalTooltipPosition( - cursorPosition.x, - cursorBandPosition, - chartDimensions, - offset.left, - isRotated, - ); - const vPosition = getVerticalTooltipPosition( - cursorPosition.y, - cursorBandPosition, - chartDimensions, - offset.top, - isRotated, - ); + const hPosition = getHorizontalTooltipPosition(cursorPosition.x, cursorBandPosition, panel, offset.left, isRotated); + const vPosition = getVerticalTooltipPosition(cursorPosition.y, cursorBandPosition, panel, offset.top, isRotated); return { isRotated, ...vPosition, @@ -199,7 +183,7 @@ export function getTooltipAnchorPosition( function getHorizontalTooltipPosition( cursorXPosition: number, cursorBandPosition: Dimensions, - chartDimensions: Dimensions, + panel: Dimensions, globalOffset: number, isRotated: boolean, ): { x0?: number; x1: number } { @@ -211,15 +195,15 @@ function getHorizontalTooltipPosition( } return { // NOTE: x0 set to zero blocks tooltip placement on left when rotated 90 deg - // Delete this comment before merging and verifing this doesn't break anything. - x1: chartDimensions.left + cursorXPosition + globalOffset, + // Delete this comment before merging and verifying this doesn't break anything. + x1: panel.left + cursorXPosition + globalOffset, }; } function getVerticalTooltipPosition( cursorYPosition: number, cursorBandPosition: Dimensions, - chartDimensions: Dimensions, + panel: Dimensions, globalOffset: number, isRotated: boolean, ): { @@ -227,7 +211,7 @@ function getVerticalTooltipPosition( y1: number; } { if (!isRotated) { - const y = cursorYPosition + chartDimensions.top + globalOffset; + const y = cursorYPosition + panel.top + globalOffset; return { y0: y, y1: y, diff --git a/src/chart_types/xy_chart/domains/x_domain.test.ts b/src/chart_types/xy_chart/domains/x_domain.test.ts index e89e4db67d..6372a3589d 100644 --- a/src/chart_types/xy_chart/domains/x_domain.test.ts +++ b/src/chart_types/xy_chart/domains/x_domain.test.ts @@ -21,7 +21,7 @@ import { ChartTypes } from '../..'; import { MockSeriesSpecs } from '../../../mocks/specs'; import { ScaleType } from '../../../scales/constants'; import { SpecTypes, Direction, BinAgg } from '../../../specs/constants'; -import { getDataSeriesBySpecId } from '../utils/series'; +import { getDataSeriesFromSpecs } from '../utils/series'; import { BasicSeriesSpec, SeriesTypes } from '../utils/specs'; import { convertXScaleTypes, findMinInterval, mergeXDomain } from './x_domain'; @@ -222,7 +222,7 @@ describe('X Domain', () => { ], }; const specDataSeries: BasicSeriesSpec[] = [ds1, ds2]; - const { xValues } = getDataSeriesBySpecId(specDataSeries); + const { xValues } = getDataSeriesFromSpecs(specDataSeries); const mergedDomain = mergeXDomain( [ { @@ -269,7 +269,7 @@ describe('X Domain', () => { }; const specDataSeries = [ds1, ds2]; - const { xValues } = getDataSeriesBySpecId(specDataSeries); + const { xValues } = getDataSeriesFromSpecs(specDataSeries); const mergedDomain = mergeXDomain( [ { @@ -316,7 +316,7 @@ describe('X Domain', () => { }; const specDataSeries = [ds1, ds2]; - const { xValues } = getDataSeriesBySpecId(specDataSeries); + const { xValues } = getDataSeriesFromSpecs(specDataSeries); const mergedDomain = mergeXDomain( [ { @@ -367,7 +367,7 @@ describe('X Domain', () => { }; const specDataSeries = [ds1, ds2]; - const { xValues } = getDataSeriesBySpecId(specDataSeries); + const { xValues } = getDataSeriesFromSpecs(specDataSeries); const mergedDomain = mergeXDomain( [ { @@ -418,7 +418,7 @@ describe('X Domain', () => { }; const specDataSeries = [ds1, ds2]; - const { xValues } = getDataSeriesBySpecId(specDataSeries); + const { xValues } = getDataSeriesFromSpecs(specDataSeries); const mergedDomain = mergeXDomain( [ { @@ -475,7 +475,7 @@ describe('X Domain', () => { min: 0, }; - const { xValues } = getDataSeriesBySpecId(specDataSeries); + const { xValues } = getDataSeriesFromSpecs(specDataSeries); const getResult = () => mergeXDomain( [ @@ -535,7 +535,7 @@ describe('X Domain', () => { }; const specDataSeries = [ds1, ds2]; - const { xValues } = getDataSeriesBySpecId(specDataSeries); + const { xValues } = getDataSeriesFromSpecs(specDataSeries); const mergedDomain = mergeXDomain( [ { @@ -586,7 +586,7 @@ describe('X Domain', () => { }; const specDataSeries = [ds1, ds2]; - const { xValues } = getDataSeriesBySpecId(specDataSeries); + const { xValues } = getDataSeriesFromSpecs(specDataSeries); const mergedDomain = mergeXDomain( [ { @@ -637,7 +637,7 @@ describe('X Domain', () => { }; const specDataSeries = [ds1, ds2]; - const { xValues } = getDataSeriesBySpecId(specDataSeries); + const { xValues } = getDataSeriesFromSpecs(specDataSeries); const mergedDomain = mergeXDomain( [ { @@ -682,7 +682,7 @@ describe('X Domain', () => { }; const specDataSeries = [ds1, ds2]; - const { xValues } = getDataSeriesBySpecId(specDataSeries); + const { xValues } = getDataSeriesFromSpecs(specDataSeries); const mergedDomain = mergeXDomain( [ @@ -891,12 +891,12 @@ describe('X Domain', () => { ]); it('should sort ordinal xValues by descending sum by default', () => { - const { xValues } = getDataSeriesBySpecId(ordinalSpecs, [], {}); + const { xValues } = getDataSeriesFromSpecs(ordinalSpecs, [], {}); expect(xValues).toEqual(new Set(['c', 'd', 'b', 'a'])); }); it('should sort ordinal xValues by descending sum', () => { - const { xValues } = getDataSeriesBySpecId(ordinalSpecs, [], { + const { xValues } = getDataSeriesFromSpecs(ordinalSpecs, [], { binAgg: BinAgg.None, direction: Direction.Descending, }); @@ -904,7 +904,7 @@ describe('X Domain', () => { }); it('should sort ordinal xValues by ascending sum', () => { - const { xValues } = getDataSeriesBySpecId(ordinalSpecs, [], { + const { xValues } = getDataSeriesFromSpecs(ordinalSpecs, [], { binAgg: BinAgg.None, direction: Direction.Ascending, }); @@ -912,12 +912,12 @@ describe('X Domain', () => { }); it('should NOT sort ordinal xValues sum', () => { - const { xValues } = getDataSeriesBySpecId(ordinalSpecs, [], undefined); + const { xValues } = getDataSeriesFromSpecs(ordinalSpecs, [], undefined); expect(xValues).toEqual(new Set(['a', 'b', 'c', 'd'])); }); it('should NOT sort ordinal xValues sum when undefined', () => { - const { xValues } = getDataSeriesBySpecId(ordinalSpecs, [], { + const { xValues } = getDataSeriesFromSpecs(ordinalSpecs, [], { binAgg: BinAgg.None, direction: Direction.Descending, }); @@ -925,7 +925,7 @@ describe('X Domain', () => { }); it('should NOT sort linear xValue by descending sum', () => { - const { xValues } = getDataSeriesBySpecId(linearSpecs, [], { + const { xValues } = getDataSeriesFromSpecs(linearSpecs, [], { direction: Direction.Descending, }); expect(xValues).toEqual(new Set([1, 2, 3, 4])); diff --git a/src/chart_types/xy_chart/domains/x_domain.ts b/src/chart_types/xy_chart/domains/x_domain.ts index 55f07d745f..1a3a054930 100644 --- a/src/chart_types/xy_chart/domains/x_domain.ts +++ b/src/chart_types/xy_chart/domains/x_domain.ts @@ -30,6 +30,7 @@ import { XDomain } from './types'; * @param specs an array of [{ seriesType, xScaleType }] * @param xValues a set of unique x values from all specs * @param customXDomain if specified, a custom xDomain + * @param fallbackScale * @returns a merged XDomain between all series. * @internal */ diff --git a/src/chart_types/xy_chart/domains/y_domain.test.ts b/src/chart_types/xy_chart/domains/y_domain.test.ts index 7402c714ff..cea56ed189 100644 --- a/src/chart_types/xy_chart/domains/y_domain.test.ts +++ b/src/chart_types/xy_chart/domains/y_domain.test.ts @@ -26,7 +26,7 @@ import { Position } from '../../../utils/commons'; import { BARCHART_1Y0G } from '../../../utils/data_samples/test_dataset'; import { computeSeriesDomainsSelector } from '../state/selectors/compute_series_domains'; import { BasicSeriesSpec, SeriesTypes, DEFAULT_GLOBAL_ID, StackMode } from '../utils/specs'; -import { coerceYScaleTypes, splitSpecsByGroupId } from './y_domain'; +import { coerceYScaleTypes, groupSeriesByYGroup } from './y_domain'; const DEMO_AREA_SPEC_1 = { id: 'a', @@ -243,7 +243,7 @@ describe('Y Domain', () => { yAccessors: ['y'], data: BARCHART_1Y0G, }; - const splittedSpecs = splitSpecsByGroupId([spec1, spec2]); + const splittedSpecs = groupSeriesByYGroup([spec1, spec2]); const groupKeys = [...splittedSpecs.keys()]; const groupValues = [...splittedSpecs.values()]; expect(groupKeys).toEqual(['group1', 'group2']); @@ -280,7 +280,7 @@ describe('Y Domain', () => { stackAccessors: ['x'], data: BARCHART_1Y0G, }; - const splittedSpecs = splitSpecsByGroupId([spec1, spec2]); + const splittedSpecs = groupSeriesByYGroup([spec1, spec2]); const groupKeys = [...splittedSpecs.keys()]; const groupValues = [...splittedSpecs.values()]; expect(groupKeys).toEqual(['group1', 'group2']); @@ -317,7 +317,7 @@ describe('Y Domain', () => { stackAccessors: ['x'], data: BARCHART_1Y0G, }; - const splittedSpecs = splitSpecsByGroupId([spec1, spec2]); + const splittedSpecs = groupSeriesByYGroup([spec1, spec2]); const groupKeys = [...splittedSpecs.keys()]; const groupValues = [...splittedSpecs.values()]; expect(groupKeys).toEqual(['group']); @@ -365,7 +365,7 @@ describe('Y Domain', () => { stackAccessors: ['x'], data: BARCHART_1Y0G, }; - const splittedSpecs = splitSpecsByGroupId([spec1, spec2, spec3]); + const splittedSpecs = groupSeriesByYGroup([spec1, spec2, spec3]); const groupKeys = [...splittedSpecs.keys()]; const groupValues = [...splittedSpecs.values()]; expect(groupKeys).toEqual(['group1', 'group2']); diff --git a/src/chart_types/xy_chart/domains/y_domain.ts b/src/chart_types/xy_chart/domains/y_domain.ts index 265edd8fac..54bc28e499 100644 --- a/src/chart_types/xy_chart/domains/y_domain.ts +++ b/src/chart_types/xy_chart/domains/y_domain.ts @@ -25,7 +25,8 @@ import { computeContinuousDataDomain } from '../../../utils/domain'; import { GroupId } from '../../../utils/ids'; import { Logger } from '../../../utils/logger'; import { isCompleteBound, isLowerBound, isUpperBound } from '../utils/axis_type_utils'; -import { DataSeries, FormattedDataSeries } from '../utils/series'; +import { groupBy } from '../utils/group_data_series'; +import { DataSeries } from '../utils/series'; import { BasicSeriesSpec, YDomainRange, DEFAULT_GLOBAL_ID, SeriesTypes, StackMode } from '../utils/specs'; import { YDomain } from './types'; @@ -34,84 +35,48 @@ export type YBasicSeriesSpec = Pick< 'id' | 'seriesType' | 'yScaleType' | 'groupId' | 'stackAccessors' | 'yScaleToDataExtent' | 'useDefaultGroupDomain' > & { stackMode?: StackMode; enableHistogramMode?: boolean }; -interface GroupSpecs { - stackMode?: StackMode; - stacked: YBasicSeriesSpec[]; - nonStacked: YBasicSeriesSpec[]; -} - /** @internal */ -export function mergeYDomain( - { - stacked, - nonStacked, - }: { - stacked: FormattedDataSeries[]; - nonStacked: FormattedDataSeries[]; - }, - specs: YBasicSeriesSpec[], - domainsByGroupId: Map, -): YDomain[] { - // group specs by group ids - const specsByGroupIds = splitSpecsByGroupId(specs); - const specsByGroupIdsEntries = [...specsByGroupIds.entries()]; - const globalId = DEFAULT_GLOBAL_ID; - - const yDomains = specsByGroupIdsEntries.map(([groupId, groupSpecs]) => { - const customDomain = domainsByGroupId.get(groupId); - const emptyDS: FormattedDataSeries = { - dataSeries: [], - groupId, - counts: { area: 0, bubble: 0, bar: 0, line: 0 }, - }; - const stackedDS = stacked.find((d) => d.groupId === groupId) ?? emptyDS; - const nonStackedDS = nonStacked.find((d) => d.groupId === groupId) ?? emptyDS; - const nonZeroBaselineSpecs = - stackedDS.counts.bar + stackedDS.counts.area + nonStackedDS.counts.bar + nonStackedDS.counts.area; - return mergeYDomainForGroup( - stackedDS.dataSeries, - nonStackedDS.dataSeries, - groupId, - groupSpecs, - nonZeroBaselineSpecs > 0, - customDomain, - ); - }); +export function mergeYDomain(dataSeries: DataSeries[], domainsByGroupId: Map): YDomain[] { + const dataSeriesByGroupId = groupBy( + dataSeries, + ({ spec: { useDefaultGroupDomain, groupId } }) => { + return useDefaultGroupDomain ? DEFAULT_GLOBAL_ID : groupId; + }, + true, + ); - const globalGroupIds: Set = specs.reduce>((acc, { groupId, useDefaultGroupDomain }) => { - if (groupId !== globalId && useDefaultGroupDomain) { - acc.add(groupId); - } - return acc; - }, new Set()); - globalGroupIds.add(globalId); + return dataSeriesByGroupId.reduce((acc, groupedDataSeries) => { + const [{ groupId }] = groupedDataSeries; - const globalYDomains = yDomains.filter((domain) => globalGroupIds.has(domain.groupId)); - let globalYDomain = [Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER]; - globalYDomains.forEach((domain) => { - globalYDomain = [Math.min(globalYDomain[0], domain.domain[0]), Math.max(globalYDomain[1], domain.domain[1])]; - }); - return yDomains.map((domain) => { - if (globalGroupIds.has(domain.groupId)) { - return { - ...domain, - domain: globalYDomain, - }; + const stacked = groupedDataSeries.filter(({ isStacked }) => isStacked); + const nonStacked = groupedDataSeries.filter(({ isStacked }) => !isStacked); + const customDomain = domainsByGroupId.get(groupId); + const hasNonZeroBaselineTypes = groupedDataSeries.some( + ({ seriesType }) => seriesType === SeriesTypes.Bar || seriesType === SeriesTypes.Area, + ); + const domain = mergeYDomainForGroup(stacked, nonStacked, hasNonZeroBaselineTypes, customDomain); + if (!domain) { + return acc; } - return domain; - }); + return [...acc, domain]; + }, []); } function mergeYDomainForGroup( stacked: DataSeries[], nonStacked: DataSeries[], - groupId: GroupId, - groupSpecs: GroupSpecs, hasZeroBaselineSpecs: boolean, customDomain?: YDomainRange, -): YDomain { - const groupYScaleType = coerceYScaleTypes([...groupSpecs.stacked, ...groupSpecs.nonStacked]); - const { stackMode } = groupSpecs; +): YDomain | null { + const dataSeries = [...stacked, ...nonStacked]; + if (dataSeries.length === 0) { + return null; + } + const yScaleTypes = dataSeries.map(({ spec: { yScaleType } }) => ({ + yScaleType, + })); + const groupYScaleType = coerceYScaleTypes(yScaleTypes); + const [{ stackMode, groupId }] = dataSeries; let domain: number[]; if (stackMode === StackMode.Percentage) { @@ -119,13 +84,10 @@ function mergeYDomainForGroup( } else { // TODO remove when removing yScaleToDataExtent const newCustomDomain = customDomain ? { ...customDomain } : {}; - const shouldScaleToExtent = - groupSpecs.stacked.some(({ yScaleToDataExtent }) => yScaleToDataExtent) || - groupSpecs.nonStacked.some(({ yScaleToDataExtent }) => yScaleToDataExtent); + const shouldScaleToExtent = dataSeries.some(({ spec: { yScaleToDataExtent } }) => yScaleToDataExtent); if (customDomain?.fit !== true && shouldScaleToExtent) { newCustomDomain.fit = true; } - // compute stacked domain const stackedDomain = computeYDomain(stacked, hasZeroBaselineSpecs); @@ -163,15 +125,16 @@ function mergeYDomainForGroup( }; } -function computeYDomain(dataseries: DataSeries[], hasZeroBaselineSpecs: boolean) { +function computeYDomain(dataSeries: DataSeries[], hasZeroBaselineSpecs: boolean) { const yValues = new Set(); - dataseries.forEach((ds) => { - ds.data.forEach((datum) => { + dataSeries.forEach(({ data }) => { + for (let i = 0; i < data.length; i++) { + const datum = data[i]; yValues.add(datum.y1); if (hasZeroBaselineSpecs && datum.y0 != null) { yValues.add(datum.y0); } - }); + } }); if (yValues.size === 0) { return []; @@ -180,17 +143,13 @@ function computeYDomain(dataseries: DataSeries[], hasZeroBaselineSpecs: boolean) } /** @internal */ -export function splitSpecsByGroupId(specs: YBasicSeriesSpec[]) { +export function groupSeriesByYGroup(specs: YBasicSeriesSpec[]) { const specsByGroupIds = new Map< GroupId, { stackMode: StackMode | undefined; stacked: YBasicSeriesSpec[]; nonStacked: YBasicSeriesSpec[] } >(); - // After mobx->redux https://github.com/elastic/elastic-charts/pull/281 we keep the specs untouched on mount - // in MobX version, the stackAccessors was programmatically added to every histogram specs - // in ReduX version, we left untouched the specs, so we have to manually check that - const isHistogramEnabled = specs.some( - ({ seriesType, enableHistogramMode }) => seriesType === SeriesTypes.Bar && enableHistogramMode, - ); + + const histogramEnabled = isHistogramEnabled(specs); // split each specs by groupId and by stacked or not specs.forEach((spec) => { const group = specsByGroupIds.get(spec.groupId) || { @@ -198,12 +157,8 @@ export function splitSpecsByGroupId(specs: YBasicSeriesSpec[]) { stacked: [], nonStacked: [], }; - // stack every bars if using histogram mode - // independenyly from lines and areas - if ( - (spec.seriesType === SeriesTypes.Bar && isHistogramEnabled) || - (spec.stackAccessors && spec.stackAccessors.length > 0) - ) { + + if (isStackedSpec(spec, histogramEnabled)) { group.stacked.push(spec); } else { group.nonStacked.push(spec); @@ -220,6 +175,53 @@ export function splitSpecsByGroupId(specs: YBasicSeriesSpec[]) { return specsByGroupIds; } +/** + * Histogram mode is forced on every specs if at least one specs has that prop flagged + * @remarks + * After mobx->redux https://github.com/elastic/elastic-charts/pull/281 we keep the specs untouched on mount + * in MobX version, the stackAccessors was programmatically added to every histogram specs + * in ReduX version, we left untouched the specs, so we have to manually check that + * @param specs + * @internal + */ +export function isHistogramEnabled(specs: YBasicSeriesSpec[]) { + return specs.some(({ seriesType, enableHistogramMode }) => seriesType === SeriesTypes.Bar && enableHistogramMode); +} + +/** + * Return true if the passed spec needs to be rendered as stack + * @param spec + * @param histogramEnabled + * @internal + */ +export function isStackedSpec(spec: YBasicSeriesSpec, histogramEnabled: boolean) { + const isBarAndHistogram = spec.seriesType === SeriesTypes.Bar && histogramEnabled; + const hasStackAccessors = spec.stackAccessors && spec.stackAccessors.length > 0; + return isBarAndHistogram || hasStackAccessors; +} + +/** + * Get the stack mode for every groupId + * @param specs + * @internal + */ +export function getStackModeForYGroup(specs: YBasicSeriesSpec[]) { + return specs.reduce>((acc, { groupId, stackMode }) => { + if (!acc[groupId]) { + acc[groupId] = undefined; + } + + if (acc[groupId] === undefined && stackMode !== undefined) { + acc[groupId] = stackMode; + } + if (stackMode !== undefined && acc[groupId] !== stackMode) { + Logger.warn(`Is not possible to mix different stackModes, please align all stackMode on the same GroupId + to the same mode. The default behaviour will be to use the first encountered stackMode on the series`); + } + return acc; + }, {}); +} + /** * Coerce the scale types of a set of specification to a generic one. * If there is at least one bar series type, than the response will specity @@ -228,13 +230,13 @@ export function splitSpecsByGroupId(specs: YBasicSeriesSpec[]) { * If there are multiple continuous scale types, is coerced to linear. * If there are at least one Ordinal scale type, is coerced to ordinal. * If none of the above, than coerce to the specified scale. - * @returns {ChartScaleType} + * @returns {ScaleContinuousType} * @internal */ -export function coerceYScaleTypes(specs: Pick[]): ScaleContinuousType { +export function coerceYScaleTypes(scales: { yScaleType: ScaleContinuousType }[]): ScaleContinuousType { const scaleTypes = new Set(); - specs.forEach((spec) => { - scaleTypes.add(spec.yScaleType); + scales.forEach(({ yScaleType }) => { + scaleTypes.add(yScaleType); }); return coerceYScale(scaleTypes); } @@ -247,3 +249,13 @@ function coerceYScale(scaleTypes: Set): ScaleContinuousType } return ScaleType.Linear; } + +export function getYScaleTypeByGroupId(specs: BasicSeriesSpec[]): Map { + const groups = groupBy(specs, ['groupId'], true); + return groups.reduce((acc, group) => { + const scaleType = coerceYScaleTypes(group); + const [{ groupId }] = group; + acc.set(groupId, scaleType); + return acc; + }, new Map()); +} diff --git a/src/chart_types/xy_chart/renderer/canvas/annotations/index.ts b/src/chart_types/xy_chart/renderer/canvas/annotations/index.ts index 1c8beea5fc..8f8f27684f 100644 --- a/src/chart_types/xy_chart/renderer/canvas/annotations/index.ts +++ b/src/chart_types/xy_chart/renderer/canvas/annotations/index.ts @@ -17,6 +17,8 @@ * under the License. */ +import { Rotation } from '../../../../../utils/commons'; +import { Dimensions } from '../../../../../utils/dimensions'; import { AnnotationId } from '../../../../../utils/ids'; import { mergeWithDefaultAnnotationLine, mergeWithDefaultAnnotationRect } from '../../../../../utils/themes/theme'; import { AnnotationLineProps } from '../../../annotations/line/types'; @@ -30,16 +32,16 @@ import { renderRectAnnotations } from './rect'; interface AnnotationProps { annotationDimensions: Map; annotationSpecs: AnnotationSpec[]; + rotation: Rotation; + renderingArea: Dimensions; } /** @internal */ export function renderAnnotations( ctx: CanvasRenderingContext2D, - props: AnnotationProps, + { annotationDimensions, annotationSpecs, rotation, renderingArea }: AnnotationProps, renderOnBackground: boolean = true, ) { - const { annotationDimensions, annotationSpecs } = props; - annotationDimensions.forEach((annotation, id) => { const spec = getSpecsById(annotationSpecs, id); if (!spec) { @@ -49,10 +51,10 @@ export function renderAnnotations( if ((isBackground && renderOnBackground) || (!isBackground && !renderOnBackground)) { if (isLineAnnotation(spec)) { const lineStyle = mergeWithDefaultAnnotationLine(spec.style); - renderLineAnnotations(ctx, annotation as AnnotationLineProps[], lineStyle); + renderLineAnnotations(ctx, annotation as AnnotationLineProps[], lineStyle, rotation, renderingArea); } else if (isRectAnnotation(spec)) { const rectStyle = mergeWithDefaultAnnotationRect(spec.style); - renderRectAnnotations(ctx, annotation as AnnotationRectProps[], rectStyle); + renderRectAnnotations(ctx, annotation as AnnotationRectProps[], rectStyle, rotation, renderingArea); } } }); diff --git a/src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts b/src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts index e9bbab59c0..abefd0e690 100644 --- a/src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts +++ b/src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts @@ -17,30 +17,23 @@ * under the License. */ -import { Stroke, Line } from '../../../../../geoms/types'; +import { Stroke } from '../../../../../geoms/types'; +import { Rotation } from '../../../../../utils/commons'; +import { Dimensions } from '../../../../../utils/dimensions'; import { LineAnnotationStyle } from '../../../../../utils/themes/theme'; import { stringToRGB } from '../../../../partition_chart/layout/utils/color_library_wrappers'; import { AnnotationLineProps } from '../../../annotations/line/types'; -import { renderMultiLine } from '../primitives/line'; +import { renderLine } from '../primitives/line'; +import { withPanelTransform } from '../utils/panel_transform'; /** @internal */ export function renderLineAnnotations( ctx: CanvasRenderingContext2D, annotations: AnnotationLineProps[], lineStyle: LineAnnotationStyle, + rotation: Rotation, + renderingArea: Dimensions, ) { - const lines = annotations.map((annotation) => { - const { - start: { x1, y1 }, - end: { x2, y2 }, - } = annotation.linePathPoints; - return { - x1, - y1, - x2, - y2, - }; - }); const strokeColor = stringToRGB(lineStyle.line.stroke); strokeColor.opacity *= lineStyle.line.opacity; const stroke: Stroke = { @@ -49,5 +42,9 @@ export function renderLineAnnotations( dash: lineStyle.line.dash, }; - renderMultiLine(ctx, lines, stroke); + annotations.forEach(({ linePathPoints, panel }) => { + withPanelTransform(ctx, panel, rotation, renderingArea, (ctx) => { + renderLine(ctx, linePathPoints, stroke); + }); + }); } diff --git a/src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts b/src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts index bb5c7e22a6..5c9704f8da 100644 --- a/src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts +++ b/src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts @@ -17,20 +17,23 @@ * under the License. */ -import { Rect, Fill, Stroke } from '../../../../../geoms/types'; -import { withContext } from '../../../../../renderers/canvas'; +import { Fill, Stroke } from '../../../../../geoms/types'; +import { Rotation } from '../../../../../utils/commons'; +import { Dimensions } from '../../../../../utils/dimensions'; import { RectAnnotationStyle } from '../../../../../utils/themes/theme'; import { stringToRGB } from '../../../../partition_chart/layout/utils/color_library_wrappers'; import { AnnotationRectProps } from '../../../annotations/rect/types'; import { renderRect } from '../primitives/rect'; +import { withPanelTransform } from '../utils/panel_transform'; /** @internal */ export function renderRectAnnotations( ctx: CanvasRenderingContext2D, annotations: AnnotationRectProps[], rectStyle: RectAnnotationStyle, + rotation: Rotation, + renderingArea: Dimensions, ) { - const rects = annotations.map(({ rect }) => rect); const fillColor = stringToRGB(rectStyle.fill); fillColor.opacity *= rectStyle.opacity; const fill: Fill = { @@ -43,11 +46,11 @@ export function renderRectAnnotations( width: rectStyle.strokeWidth, }; - const rectsLength = rects.length; + const rectsLength = annotations.length; for (let i = 0; i < rectsLength; i++) { - const rect = rects[i]; - withContext(ctx, (ctx) => { + const { rect, panel } = annotations[i]; + withPanelTransform(ctx, panel, rotation, renderingArea, (ctx) => { renderRect(ctx, rect, fill, stroke); }); } diff --git a/src/chart_types/xy_chart/renderer/canvas/areas.ts b/src/chart_types/xy_chart/renderer/canvas/areas.ts index 8710b84161..91e24a7f2c 100644 --- a/src/chart_types/xy_chart/renderer/canvas/areas.ts +++ b/src/chart_types/xy_chart/renderer/canvas/areas.ts @@ -19,64 +19,77 @@ import { LegendItem } from '../../../../commons/legend'; import { Rect } from '../../../../geoms/types'; -import { withClip, withContext } from '../../../../renderers/canvas'; -import { AreaGeometry } from '../../../../utils/geometry'; +import { withContext } from '../../../../renderers/canvas'; +import { Rotation } from '../../../../utils/commons'; +import { Dimensions } from '../../../../utils/dimensions'; +import { AreaGeometry, PerPanel } from '../../../../utils/geometry'; import { SharedGeometryStateStyle } from '../../../../utils/themes/theme'; import { getGeometryStateStyle } from '../../rendering/rendering'; import { renderPoints } from './points'; import { renderLinePaths, renderAreaPath } from './primitives/path'; import { buildAreaStyles } from './styles/area'; import { buildLineStyles } from './styles/line'; +import { withPanelTransform } from './utils/panel_transform'; interface AreaGeometriesProps { - areas: AreaGeometry[]; + areas: Array>; sharedStyle: SharedGeometryStateStyle; - highlightedLegendItem: LegendItem | null; + rotation: Rotation; + renderingArea: Dimensions; + highlightedLegendItem?: LegendItem; clippings: Rect; } /** @internal */ export function renderAreas(ctx: CanvasRenderingContext2D, props: AreaGeometriesProps) { - withContext(ctx, (ctx) => { - const { sharedStyle, highlightedLegendItem, areas, clippings } = props; - - withClip(ctx, clippings, (ctx: CanvasRenderingContext2D) => { - ctx.save(); + const { sharedStyle, highlightedLegendItem, areas, clippings, rotation, renderingArea } = props; - // eslint-disable-next-line no-restricted-syntax - for (const glyph of areas) { - const { seriesAreaLineStyle, seriesAreaStyle } = glyph; - if (seriesAreaStyle.visible) { - withContext(ctx, () => { - renderArea(ctx, glyph, sharedStyle, highlightedLegendItem, clippings); - }); - } - if (seriesAreaLineStyle.visible) { - withContext(ctx, () => { - renderAreaLines(ctx, glyph, sharedStyle, highlightedLegendItem, clippings); - }); - } + withContext(ctx, (ctx) => { + areas.forEach(({ panel, value: area }) => { + const { seriesAreaLineStyle, seriesAreaStyle } = area; + if (seriesAreaStyle.visible) { + withPanelTransform( + ctx, + panel, + rotation, + renderingArea, + (ctx) => { + renderArea(ctx, area, sharedStyle, clippings, highlightedLegendItem); + }, + { area: clippings, shouldClip: true }, + ); } - ctx.rect(clippings.x, clippings.y, clippings.width, clippings.height); - ctx.clip(); - ctx.restore(); - }); - - areas.forEach((area) => { - const { seriesPointStyle, seriesIdentifier } = area; - if (seriesPointStyle.visible) { - const geometryStateStyle = getGeometryStateStyle(seriesIdentifier, highlightedLegendItem, sharedStyle); - withClip( + if (seriesAreaLineStyle.visible) { + withPanelTransform( ctx, - clippings, + panel, + rotation, + renderingArea, (ctx) => { - renderPoints(ctx, area.points, seriesPointStyle, geometryStateStyle); + renderAreaLines(ctx, area, sharedStyle, clippings, highlightedLegendItem); }, - // TODO: add padding over clipping - area.points[0]?.value.mark !== null, + { area: clippings, shouldClip: true }, ); } }); + + areas.forEach(({ panel, value: area }) => { + const { seriesPointStyle, seriesIdentifier } = area; + if (!seriesPointStyle.visible) { + return; + } + const geometryStateStyle = getGeometryStateStyle(seriesIdentifier, sharedStyle, highlightedLegendItem); + withPanelTransform( + ctx, + panel, + rotation, + renderingArea, + (ctx) => { + renderPoints(ctx, area.points, seriesPointStyle, geometryStateStyle); + }, + { area: clippings, shouldClip: area.points[0]?.value.mark !== null }, + ); + }); }); } @@ -84,24 +97,24 @@ function renderArea( ctx: CanvasRenderingContext2D, glyph: AreaGeometry, sharedStyle: SharedGeometryStateStyle, - highlightedLegendItem: LegendItem | null, clippings: Rect, + highlightedLegendItem?: LegendItem, ) { const { area, color, transform, seriesIdentifier, seriesAreaStyle, clippedRanges, hideClippedRanges } = glyph; - const geometryStateStyle = getGeometryStateStyle(seriesIdentifier, highlightedLegendItem, sharedStyle); + const geometryStateStyle = getGeometryStateStyle(seriesIdentifier, sharedStyle, highlightedLegendItem); const fill = buildAreaStyles(color, seriesAreaStyle, geometryStateStyle); - renderAreaPath(ctx, transform.x, area, fill, clippedRanges, clippings, hideClippedRanges); + renderAreaPath(ctx, transform, area, fill, clippedRanges, clippings, hideClippedRanges); } function renderAreaLines( ctx: CanvasRenderingContext2D, glyph: AreaGeometry, sharedStyle: SharedGeometryStateStyle, - highlightedLegendItem: LegendItem | null, clippings: Rect, + highlightedLegendItem?: LegendItem, ) { const { lines, color, seriesIdentifier, transform, seriesAreaLineStyle, clippedRanges, hideClippedRanges } = glyph; - const geometryStateStyle = getGeometryStateStyle(seriesIdentifier, highlightedLegendItem, sharedStyle); + const geometryStateStyle = getGeometryStateStyle(seriesIdentifier, sharedStyle, highlightedLegendItem); const stroke = buildLineStyles(color, seriesAreaLineStyle, geometryStateStyle); - renderLinePaths(ctx, transform.x, lines, stroke, clippedRanges, clippings, hideClippedRanges); + renderLinePaths(ctx, transform, lines, stroke, clippedRanges, clippings, hideClippedRanges); } diff --git a/src/chart_types/xy_chart/renderer/canvas/axes/index.ts b/src/chart_types/xy_chart/renderer/canvas/axes/index.ts index f73a6f1648..35b1cbd34f 100644 --- a/src/chart_types/xy_chart/renderer/canvas/axes/index.ts +++ b/src/chart_types/xy_chart/renderer/canvas/axes/index.ts @@ -18,10 +18,12 @@ */ import { withContext } from '../../../../../renderers/canvas'; -import { Dimensions } from '../../../../../utils/dimensions'; -import { AxisId } from '../../../../../utils/ids'; +import { Dimensions, Size } from '../../../../../utils/dimensions'; +import { Point } from '../../../../../utils/point'; import { AxisStyle } from '../../../../../utils/themes/theme'; +import { PerPanelAxisGeoms } from '../../../state/selectors/compute_per_panel_axes_geoms'; import { getSpecsById } from '../../../state/utils/spec'; +import { isVerticalAxis } from '../../../utils/axis_type_utils'; import { AxisTick, AxisTicksDimensions, shouldShowTicks } from '../../../utils/axis_utils'; import { AxisSpec } from '../../../utils/specs'; import { renderDebugRect } from '../utils/debug'; @@ -32,73 +34,80 @@ import { renderTitle } from './title'; /** @internal */ export interface AxisProps { + title?: string; + panelAnchor: Point; axisStyle: AxisStyle; axisSpec: AxisSpec; - axisTicksDimensions: AxisTicksDimensions; - axisPosition: Dimensions; + size: Size; + anchorPoint: Point; + dimension: AxisTicksDimensions; ticks: AxisTick[]; debug: boolean; - chartDimensions: Dimensions; + renderingArea: Dimensions; } /** @internal */ export interface AxesProps { - axesVisibleTicks: Map; axesSpecs: AxisSpec[]; - axesTicksDimensions: Map; - axesPositions: Map; + perPanelAxisGeoms: PerPanelAxisGeoms[]; axesStyles: Map; sharedAxesStyle: AxisStyle; debug: boolean; - chartDimensions: Dimensions; + renderingArea: Dimensions; } /** @internal */ export function renderAxes(ctx: CanvasRenderingContext2D, props: AxesProps) { - const { - axesVisibleTicks, - axesSpecs, - axesTicksDimensions, - axesPositions, - axesStyles, - sharedAxesStyle, - debug, - chartDimensions, - } = props; - axesVisibleTicks.forEach((ticks, axisId) => { - const axisSpec = getSpecsById(axesSpecs, axisId); - const axisTicksDimensions = axesTicksDimensions.get(axisId); - const axisPosition = axesPositions.get(axisId); - - if (!ticks || !axisSpec || !axisTicksDimensions || !axisPosition || axisSpec.hide) { - return; - } + const { axesSpecs, perPanelAxisGeoms, axesStyles, sharedAxesStyle, debug, renderingArea } = props; + perPanelAxisGeoms.forEach(({ axesGeoms, panelAnchor }) => { + withContext(ctx, (ctx) => { + axesGeoms.forEach((geometry) => { + const { + axis: { title, id, position }, + anchorPoint, + size, + dimension, + visibleTicks: ticks, + } = geometry; + const axisSpec = getSpecsById(axesSpecs, id); - const axisStyle = axesStyles.get(axisSpec.id) ?? sharedAxesStyle; + if (!axisSpec || !dimension || !position || axisSpec.hide) { + return; + } - renderAxis(ctx, { - axisSpec, - axisTicksDimensions, - axisPosition, - ticks, - axisStyle, - debug, - chartDimensions, + const axisStyle = axesStyles.get(axisSpec.id) ?? sharedAxesStyle; + renderAxis(ctx, { + title, + panelAnchor, + axisSpec, + anchorPoint, + size, + dimension, + ticks, + axisStyle, + debug, + renderingArea, + }); + }); }); }); } function renderAxis(ctx: CanvasRenderingContext2D, props: AxisProps) { withContext(ctx, (ctx) => { - const { ticks, axisPosition, debug, axisStyle, axisSpec } = props; + const { ticks, size, anchorPoint, debug, axisStyle, axisSpec, panelAnchor } = props; const showTicks = shouldShowTicks(axisStyle.tickLine, axisSpec.hide); - ctx.translate(axisPosition.left, axisPosition.top); + const isVertical = isVerticalAxis(axisSpec.position); + const translate = { + y: isVertical ? anchorPoint.y + panelAnchor.y : anchorPoint.y, + x: isVertical ? anchorPoint.x : anchorPoint.x + panelAnchor.x, + }; + ctx.translate(translate.x, translate.y); if (debug) { renderDebugRect(ctx, { x: 0, y: 0, - width: axisPosition.width, - height: axisPosition.height, + ...size, }); } diff --git a/src/chart_types/xy_chart/renderer/canvas/axes/line.ts b/src/chart_types/xy_chart/renderer/canvas/axes/line.ts index d0b09fc069..a0d4109c2b 100644 --- a/src/chart_types/xy_chart/renderer/canvas/axes/line.ts +++ b/src/chart_types/xy_chart/renderer/canvas/axes/line.ts @@ -24,7 +24,7 @@ import { isVerticalAxis } from '../../../utils/axis_type_utils'; /** @internal */ export function renderLine( ctx: CanvasRenderingContext2D, - { axisSpec: { position }, axisPosition, axisStyle: { axisLine } }: AxisProps, + { axisSpec: { position }, size, axisStyle: { axisLine } }: AxisProps, ) { if (!axisLine.visible) { return; @@ -32,15 +32,15 @@ export function renderLine( const lineProps: number[] = []; if (isVerticalAxis(position)) { - lineProps[0] = position === Position.Left ? axisPosition.width : 0; - lineProps[2] = position === Position.Left ? axisPosition.width : 0; + lineProps[0] = position === Position.Left ? size.width : 0; + lineProps[2] = position === Position.Left ? size.width : 0; lineProps[1] = 0; - lineProps[3] = axisPosition.height; + lineProps[3] = size.height; } else { lineProps[0] = 0; - lineProps[2] = axisPosition.width; - lineProps[1] = position === Position.Top ? axisPosition.height : 0; - lineProps[3] = position === Position.Top ? axisPosition.height : 0; + lineProps[2] = size.width; + lineProps[1] = position === Position.Top ? size.height : 0; + lineProps[3] = position === Position.Top ? size.height : 0; } ctx.beginPath(); ctx.moveTo(lineProps[0], lineProps[1]); diff --git a/src/chart_types/xy_chart/renderer/canvas/axes/tick.ts b/src/chart_types/xy_chart/renderer/canvas/axes/tick.ts index 4ef06739ab..e26832a53b 100644 --- a/src/chart_types/xy_chart/renderer/canvas/axes/tick.ts +++ b/src/chart_types/xy_chart/renderer/canvas/axes/tick.ts @@ -29,13 +29,13 @@ import { renderLine } from '../primitives/line'; export function renderTick(ctx: CanvasRenderingContext2D, tick: AxisTick, props: AxisProps) { const { axisSpec: { position }, - axisPosition, + size, axisStyle: { tickLine }, } = props; if (isVerticalAxis(position)) { - renderVerticalTick(ctx, position, axisPosition.width, tickLine.size, tick.position, tickLine); + renderVerticalTick(ctx, position, size.width, tickLine.size, tick.position, tickLine); } else { - renderHorizontalTick(ctx, position, axisPosition.height, tickLine.size, tick.position, tickLine); + renderHorizontalTick(ctx, position, size.height, tickLine.size, tick.position, tickLine); } } diff --git a/src/chart_types/xy_chart/renderer/canvas/axes/tick_label.ts b/src/chart_types/xy_chart/renderer/canvas/axes/tick_label.ts index c4b646e61d..b855ac8418 100644 --- a/src/chart_types/xy_chart/renderer/canvas/axes/tick_label.ts +++ b/src/chart_types/xy_chart/renderer/canvas/axes/tick_label.ts @@ -28,8 +28,8 @@ import { renderDebugRectCenterRotated } from '../utils/debug'; export function renderTickLabel(ctx: CanvasRenderingContext2D, tick: AxisTick, showTicks: boolean, props: AxisProps) { const { axisSpec: { position, labelFormat }, - axisTicksDimensions, - axisPosition, + dimension: axisTicksDimensions, + size, debug, axisStyle, } = props; @@ -41,7 +41,7 @@ export function renderTickLabel(ctx: CanvasRenderingContext2D, tick: AxisTick, s axisStyle, tick.position, position, - axisPosition, + size, axisTicksDimensions, showTicks, offset, diff --git a/src/chart_types/xy_chart/renderer/canvas/axes/title.ts b/src/chart_types/xy_chart/renderer/canvas/axes/title.ts index 8cf282a903..c000b0df5c 100644 --- a/src/chart_types/xy_chart/renderer/canvas/axes/title.ts +++ b/src/chart_types/xy_chart/renderer/canvas/axes/title.ts @@ -43,11 +43,12 @@ export function renderTitle(ctx: CanvasRenderingContext2D, props: AxisProps) { function renderVerticalTitle(ctx: CanvasRenderingContext2D, props: AxisProps) { const { - axisPosition: { height }, - axisSpec: { title, position, hide: hideAxis }, - axisTicksDimensions: { maxLabelBboxWidth }, + size: { height }, + axisSpec: { position, hide: hideAxis }, + dimension: { maxLabelBboxWidth }, axisStyle: { axisTitle, tickLine, tickLabel }, debug, + title, } = props; if (!title) { return null; @@ -84,11 +85,12 @@ function renderVerticalTitle(ctx: CanvasRenderingContext2D, props: AxisProps) { } function renderHorizontalTitle(ctx: CanvasRenderingContext2D, props: AxisProps) { const { - axisPosition: { width }, - axisSpec: { title, position, hide: hideAxis }, - axisTicksDimensions: { maxLabelBboxHeight }, + size: { width }, + axisSpec: { position, hide: hideAxis }, + dimension: { maxLabelBboxHeight }, axisStyle: { axisTitle, tickLine, tickLabel }, debug, + title, } = props; if (!title) { diff --git a/src/chart_types/xy_chart/renderer/canvas/bars.ts b/src/chart_types/xy_chart/renderer/canvas/bars.ts index 8cc18dbb3c..750b668422 100644 --- a/src/chart_types/xy_chart/renderer/canvas/bars.ts +++ b/src/chart_types/xy_chart/renderer/canvas/bars.ts @@ -19,35 +19,58 @@ import { LegendItem } from '../../../../commons/legend'; import { Rect } from '../../../../geoms/types'; -import { withContext, withClip } from '../../../../renderers/canvas'; -import { BarGeometry } from '../../../../utils/geometry'; +import { withContext } from '../../../../renderers/canvas'; +import { Rotation } from '../../../../utils/commons'; +import { Dimensions } from '../../../../utils/dimensions'; +import { BarGeometry, PerPanel } from '../../../../utils/geometry'; import { SharedGeometryStateStyle } from '../../../../utils/themes/theme'; import { getGeometryStateStyle } from '../../rendering/rendering'; import { renderRect } from './primitives/rect'; import { buildBarStyles } from './styles/bar'; +import { withPanelTransform } from './utils/panel_transform'; /** @internal */ export function renderBars( ctx: CanvasRenderingContext2D, - barGeometries: BarGeometry[], + barGeometries: Array>, sharedStyle: SharedGeometryStateStyle, clippings: Rect, + renderingArea: Dimensions, highlightedLegendItem?: LegendItem, + rotation?: Rotation, ) { withContext(ctx, (ctx) => { - withClip(ctx, clippings, (ctx: CanvasRenderingContext2D) => { - // ctx.scale(1, -1); // D3 and Canvas2d use a left-handed coordinate system (+y = down) but the ViewModel uses +y = up, so we must locally invert Y - barGeometries.forEach((barGeometry) => { - const { x, y, width, height, color, seriesStyle } = barGeometry; - const geometryStateStyle = getGeometryStateStyle( - barGeometry.seriesIdentifier, - highlightedLegendItem || null, - sharedStyle, - ); - const { fill, stroke } = buildBarStyles(color, seriesStyle.rect, seriesStyle.rectBorder, geometryStateStyle); - const rect = { x, y, width, height }; - renderRect(ctx, rect, fill, stroke); - }); - }); + const barRenderer = renderPerPanelBars(ctx, clippings, sharedStyle, renderingArea, highlightedLegendItem, rotation); + barGeometries.forEach(barRenderer); }); } + +function renderPerPanelBars( + ctx: CanvasRenderingContext2D, + clippings: Rect, + sharedStyle: SharedGeometryStateStyle, + renderingArea: Dimensions, + highlightedLegendItem?: LegendItem, + rotation: Rotation = 0, +) { + return ({ panel, value: bars }: PerPanel) => { + withPanelTransform( + ctx, + panel, + rotation, + renderingArea, + (ctx) => { + bars.forEach((barGeometry) => { + const { x, y, width, height, color, seriesStyle, seriesIdentifier } = barGeometry; + const geometryStateStyle = getGeometryStateStyle(seriesIdentifier, sharedStyle, highlightedLegendItem); + const { fill, stroke } = buildBarStyles(color, seriesStyle.rect, seriesStyle.rectBorder, geometryStateStyle); + const rect = { x, y, width, height }; + withContext(ctx, (ctx) => { + renderRect(ctx, rect, fill, stroke); + }); + }); + }, + { area: clippings, shouldClip: true }, + ); + }; +} diff --git a/src/chart_types/xy_chart/renderer/canvas/bubbles.ts b/src/chart_types/xy_chart/renderer/canvas/bubbles.ts index ae8b6de34f..fe3ea4d0c8 100644 --- a/src/chart_types/xy_chart/renderer/canvas/bubbles.ts +++ b/src/chart_types/xy_chart/renderer/canvas/bubbles.ts @@ -20,42 +20,54 @@ import { LegendItem } from '../../../../commons/legend'; import { SeriesKey } from '../../../../commons/series_id'; import { Rect } from '../../../../geoms/types'; -import { withContext, withClip } from '../../../../renderers/canvas'; -import { BubbleGeometry, PointGeometry } from '../../../../utils/geometry'; +import { withContext } from '../../../../renderers/canvas'; +import { Rotation } from '../../../../utils/commons'; +import { Dimensions } from '../../../../utils/dimensions'; +import { BubbleGeometry, PerPanel, PointGeometry } from '../../../../utils/geometry'; import { SharedGeometryStateStyle, GeometryStateStyle, PointStyle } from '../../../../utils/themes/theme'; import { getGeometryStateStyle } from '../../rendering/rendering'; import { renderPointGroup } from './points'; interface BubbleGeometriesDataProps { animated?: boolean; - bubbles: BubbleGeometry[]; + bubbles: Array>; sharedStyle: SharedGeometryStateStyle; - highlightedLegendItem: LegendItem | null; + highlightedLegendItem?: LegendItem; clippings: Rect; + rotation: Rotation; + renderingArea: Dimensions; } /** @internal */ export function renderBubbles(ctx: CanvasRenderingContext2D, props: BubbleGeometriesDataProps) { withContext(ctx, (ctx) => { - const { bubbles, sharedStyle, highlightedLegendItem, clippings } = props; + const { bubbles, sharedStyle, highlightedLegendItem, clippings, rotation, renderingArea } = props; const geometryStyles: Record = {}; const pointStyles: Record = {}; - const allPoints = bubbles.reduce((acc, { seriesIdentifier, seriesPointStyle, points }) => { - const geometryStyle = getGeometryStateStyle(seriesIdentifier, highlightedLegendItem, sharedStyle); - geometryStyles[seriesIdentifier.key] = geometryStyle; - pointStyles[seriesIdentifier.key] = seriesPointStyle; + const allPoints = bubbles.reduce( + (acc, { value: { seriesIdentifier, seriesPointStyle, points } }) => { + geometryStyles[seriesIdentifier.key] = getGeometryStateStyle( + seriesIdentifier, + sharedStyle, + highlightedLegendItem, + ); + pointStyles[seriesIdentifier.key] = seriesPointStyle; - acc.push(...points); - return acc; - }, []); + acc.push(...points); + return acc; + }, + [], + ); - withClip( + renderPointGroup( ctx, + allPoints, + pointStyles, + geometryStyles, + rotation, + renderingArea, clippings, - (ctx) => { - renderPointGroup(ctx, allPoints, pointStyles, geometryStyles); - }, // TODO: add padding over clipping allPoints[0]?.value.mark !== null, ); diff --git a/src/chart_types/xy_chart/renderer/canvas/grids.ts b/src/chart_types/xy_chart/renderer/canvas/grids.ts index 22e056ca92..bb911fb530 100644 --- a/src/chart_types/xy_chart/renderer/canvas/grids.ts +++ b/src/chart_types/xy_chart/renderer/canvas/grids.ts @@ -17,56 +17,37 @@ * under the License. */ -import { Line, Stroke } from '../../../../geoms/types'; import { withContext } from '../../../../renderers/canvas'; -import { mergePartial } from '../../../../utils/commons'; import { Dimensions } from '../../../../utils/dimensions'; -import { AxisId } from '../../../../utils/ids'; import { AxisStyle } from '../../../../utils/themes/theme'; -import { stringToRGB } from '../../../partition_chart/layout/utils/color_library_wrappers'; -import { getSpecsById } from '../../state/utils/spec'; -import { isVerticalGrid } from '../../utils/axis_type_utils'; -import { AxisLinePosition } from '../../utils/axis_utils'; +import { LinesGrid } from '../../utils/grid_lines'; import { AxisSpec } from '../../utils/specs'; -import { renderMultiLine, MIN_STROKE_WIDTH } from './primitives/line'; +import { renderMultiLine } from './primitives/line'; interface GridProps { sharedAxesStyle: AxisStyle; - axesGridLinesPositions: Map; + perPanelGridLines: Array; axesSpecs: AxisSpec[]; - chartDimensions: Dimensions; + renderingArea: Dimensions; axesStyles: Map; } /** @internal */ export function renderGrids(ctx: CanvasRenderingContext2D, props: GridProps) { - const { axesGridLinesPositions, axesSpecs, chartDimensions, sharedAxesStyle, axesStyles } = props; + const { + perPanelGridLines, + renderingArea: { left, top }, + } = props; withContext(ctx, (ctx) => { - ctx.translate(chartDimensions.left, chartDimensions.top); - axesGridLinesPositions.forEach((axisGridLinesPositions, axisId) => { - const axisSpec = getSpecsById(axesSpecs, axisId); - if (axisSpec && axisGridLinesPositions.length > 0) { - const axisStyle = axesStyles.get(axisSpec.id) ?? sharedAxesStyle; - const themeConfig = isVerticalGrid(axisSpec.position) - ? axisStyle.gridLine.vertical - : axisStyle.gridLine.horizontal; + ctx.translate(left, top); - const axisSpecConfig = axisSpec.gridLine; - const gridLine = axisSpecConfig ? mergePartial(themeConfig, axisSpecConfig) : themeConfig; - if (!gridLine.stroke || !gridLine.strokeWidth || gridLine.strokeWidth < MIN_STROKE_WIDTH) { - return; - } - const strokeColor = stringToRGB(gridLine.stroke); - strokeColor.opacity = - gridLine.opacity !== undefined ? strokeColor.opacity * gridLine.opacity : strokeColor.opacity; - const stroke: Stroke = { - color: strokeColor, - width: gridLine.strokeWidth, - dash: gridLine.dash, - }; - const lines = axisGridLinesPositions.map(([x1, y1, x2, y2]) => ({ x1, y1, x2, y2 })); - renderMultiLine(ctx, lines, stroke); - } + perPanelGridLines.forEach(({ lineGroups, panelAnchor: { x, y } }) => { + withContext(ctx, (ctx) => { + ctx.translate(x, y); + lineGroups.forEach(({ lines, stroke }) => { + renderMultiLine(ctx, lines, stroke); + }); + }); }); }); } diff --git a/src/chart_types/xy_chart/renderer/canvas/lines.ts b/src/chart_types/xy_chart/renderer/canvas/lines.ts index 4a38acda3f..db631a2b8f 100644 --- a/src/chart_types/xy_chart/renderer/canvas/lines.ts +++ b/src/chart_types/xy_chart/renderer/canvas/lines.ts @@ -19,46 +19,53 @@ import { LegendItem } from '../../../../commons/legend'; import { Rect } from '../../../../geoms/types'; -import { withContext, withClip } from '../../../../renderers/canvas'; -import { LineGeometry } from '../../../../utils/geometry'; +import { withContext } from '../../../../renderers/canvas'; +import { Rotation } from '../../../../utils/commons'; +import { Dimensions } from '../../../../utils/dimensions'; +import { LineGeometry, PerPanel } from '../../../../utils/geometry'; import { SharedGeometryStateStyle } from '../../../../utils/themes/theme'; import { getGeometryStateStyle } from '../../rendering/rendering'; import { renderPoints } from './points'; import { renderLinePaths } from './primitives/path'; import { buildLineStyles } from './styles/line'; +import { withPanelTransform } from './utils/panel_transform'; interface LineGeometriesDataProps { animated?: boolean; - lines: LineGeometry[]; + lines: Array>; + renderingArea: Dimensions; + rotation: Rotation; sharedStyle: SharedGeometryStateStyle; - highlightedLegendItem: LegendItem | null; + highlightedLegendItem?: LegendItem; clippings: Rect; } /** @internal */ export function renderLines(ctx: CanvasRenderingContext2D, props: LineGeometriesDataProps) { withContext(ctx, (ctx) => { - const { lines, sharedStyle, highlightedLegendItem, clippings } = props; + const { lines, sharedStyle, highlightedLegendItem, clippings, renderingArea, rotation } = props; - lines.forEach((line) => { + lines.forEach(({ panel, value: line }) => { const { seriesLineStyle, seriesPointStyle } = line; if (seriesLineStyle.visible) { - withContext(ctx, (ctx) => { - renderLine(ctx, line, highlightedLegendItem, sharedStyle, clippings); + withPanelTransform(ctx, panel, rotation, renderingArea, (ctx) => { + renderLine(ctx, line, sharedStyle, clippings, highlightedLegendItem); }); } if (seriesPointStyle.visible) { - withClip( + withPanelTransform( ctx, - clippings, + panel, + rotation, + renderingArea, (ctx) => { - const geometryStyle = getGeometryStateStyle(line.seriesIdentifier, highlightedLegendItem, sharedStyle); + const geometryStyle = getGeometryStateStyle(line.seriesIdentifier, sharedStyle, highlightedLegendItem); renderPoints(ctx, line.points, line.seriesPointStyle, geometryStyle); }, // TODO: add padding over clipping - line.points[0]?.value.mark !== null, + { area: clippings, shouldClip: line.points[0]?.value.mark !== null }, ); } }); @@ -68,12 +75,12 @@ export function renderLines(ctx: CanvasRenderingContext2D, props: LineGeometries function renderLine( ctx: CanvasRenderingContext2D, line: LineGeometry, - highlightedLegendItem: LegendItem | null, sharedStyle: SharedGeometryStateStyle, clippings: Rect, + highlightedLegendItem?: LegendItem, ) { const { color, transform, seriesIdentifier, seriesLineStyle, clippedRanges, hideClippedRanges } = line; - const geometryStyle = getGeometryStateStyle(seriesIdentifier, highlightedLegendItem, sharedStyle); + const geometryStyle = getGeometryStateStyle(seriesIdentifier, sharedStyle, highlightedLegendItem); const stroke = buildLineStyles(color, seriesLineStyle, geometryStyle); - renderLinePaths(ctx, transform.x, [line.line], stroke, clippedRanges, clippings, hideClippedRanges); + renderLinePaths(ctx, transform, [line.line], stroke, clippedRanges, clippings, hideClippedRanges); } diff --git a/src/chart_types/xy_chart/renderer/canvas/panels/panels.ts b/src/chart_types/xy_chart/renderer/canvas/panels/panels.ts new file mode 100644 index 0000000000..20a87cf018 --- /dev/null +++ b/src/chart_types/xy_chart/renderer/canvas/panels/panels.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { withContext } from '../../../../../renderers/canvas'; +import { Point } from '../../../../../utils/point'; +import { stringToRGB } from '../../../../partition_chart/layout/utils/color_library_wrappers'; +import { PanelGeoms } from '../../../state/selectors/compute_panels'; +import { renderRect } from '../primitives/rect'; + +/** @internal */ +export function renderGridPanels(ctx: CanvasRenderingContext2D, chartAnchor: Point, panels: PanelGeoms) { + withContext(ctx, (ctx) => { + ctx.translate(chartAnchor.x, chartAnchor.y); + panels.forEach((panel) => { + withContext(ctx, (ctx) => { + ctx.translate(panel.panelAnchor.x, panel.panelAnchor.y); + withContext(ctx, (ctx) => { + renderRect( + ctx, + { x: 0, y: 0, ...panel }, + { color: stringToRGB('#00000000') }, + { color: stringToRGB('#000000'), width: 1 }, + ); + }); + }); + }); + }); +} diff --git a/src/chart_types/xy_chart/renderer/canvas/points.ts b/src/chart_types/xy_chart/renderer/canvas/points.ts index 3db4866d2c..42a9817f2b 100644 --- a/src/chart_types/xy_chart/renderer/canvas/points.ts +++ b/src/chart_types/xy_chart/renderer/canvas/points.ts @@ -18,11 +18,14 @@ */ import { SeriesKey } from '../../../../commons/series_id'; -import { Circle, Stroke, Fill } from '../../../../geoms/types'; +import { Circle, Stroke, Fill, Rect } from '../../../../geoms/types'; +import { Rotation } from '../../../../utils/commons'; +import { Dimensions } from '../../../../utils/dimensions'; import { PointGeometry } from '../../../../utils/geometry'; import { PointStyle, GeometryStateStyle } from '../../../../utils/themes/theme'; import { renderCircle } from './primitives/arc'; import { buildPointStyles } from './styles/point'; +import { withPanelTransform } from './utils/panel_transform'; /** * Renders points from single series @@ -48,7 +51,7 @@ export function renderPoints( const circle: Circle = { x: x + transform.x, - y, + y: y + transform.y, radius, }; @@ -68,9 +71,13 @@ export function renderPointGroup( points: PointGeometry[], themeStyles: Record, geometryStateStyles: Record, + rotation: Rotation, + renderingArea: Dimensions, + clippings: Rect, + shouldClip: boolean, ) { points - .map<[Circle, Fill, Stroke]>((point) => { + .map<[Circle, Fill, Stroke, Dimensions]>((point) => { const { x, y, @@ -79,6 +86,7 @@ export function renderPointGroup( transform, styleOverrides, seriesIdentifier: { key }, + panel, } = point; const { fill, stroke, radius } = buildPointStyles( color, @@ -94,8 +102,19 @@ export function renderPointGroup( radius, }; - return [circle, fill, stroke]; + return [circle, fill, stroke, panel]; }) .sort(([{ radius: a }], [{ radius: b }]) => b - a) - .forEach((args) => renderCircle(ctx, ...args)); + .forEach(([circle, fill, stroke, panel]) => { + withPanelTransform( + ctx, + panel, + rotation, + renderingArea, + (ctx) => { + renderCircle(ctx, circle, fill, stroke); + }, + { area: clippings, shouldClip }, + ); + }); } diff --git a/src/chart_types/xy_chart/renderer/canvas/primitives/line.ts b/src/chart_types/xy_chart/renderer/canvas/primitives/line.ts index 092d17211a..46855fb4cd 100644 --- a/src/chart_types/xy_chart/renderer/canvas/primitives/line.ts +++ b/src/chart_types/xy_chart/renderer/canvas/primitives/line.ts @@ -29,21 +29,7 @@ export const MIN_STROKE_WIDTH = 0.001; /** @internal */ export function renderLine(ctx: CanvasRenderingContext2D, line: Line, stroke: Stroke) { - if (stroke.width < MIN_STROKE_WIDTH) { - return; - } - withContext(ctx, (ctx) => { - if (stroke.dash) { - ctx.setLineDash(stroke.dash); - } - const { x1, y1, x2, y2 } = line; - ctx.strokeStyle = RGBtoString(stroke.color); - ctx.lineWidth = stroke.width; - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.stroke(); - }); + renderMultiLine(ctx, [line], stroke); } /** @internal */ diff --git a/src/chart_types/xy_chart/renderer/canvas/primitives/path.ts b/src/chart_types/xy_chart/renderer/canvas/primitives/path.ts index 399ca82e3d..c16fa5c8c1 100644 --- a/src/chart_types/xy_chart/renderer/canvas/primitives/path.ts +++ b/src/chart_types/xy_chart/renderer/canvas/primitives/path.ts @@ -20,13 +20,14 @@ import { Rect, Stroke, Fill } from '../../../../../geoms/types'; import { withContext, withClipRanges } from '../../../../../renderers/canvas'; import { ClippedRanges } from '../../../../../utils/geometry'; +import { Point } from '../../../../../utils/point'; import { RGBtoString } from '../../../../partition_chart/layout/utils/color_library_wrappers'; import { MIN_STROKE_WIDTH } from './line'; /** @internal */ export function renderLinePaths( context: CanvasRenderingContext2D, - transformX: number, + transform: Point, linePaths: Array, stroke: Stroke, clippedRanges: ClippedRanges, @@ -35,7 +36,7 @@ export function renderLinePaths( ) { if (clippedRanges.length > 0) { withClipRanges(context, clippedRanges, clippings, false, (ctx) => { - ctx.translate(transformX, 0); + ctx.translate(transform.x, transform.y); linePaths.forEach((path) => { renderPathStroke(ctx, path, stroke); }); @@ -44,7 +45,7 @@ export function renderLinePaths( return; } withClipRanges(context, clippedRanges, clippings, true, (ctx) => { - ctx.translate(transformX, 0); + ctx.translate(transform.x, transform.y); linePaths.forEach((path) => { renderPathStroke(ctx, path, { ...stroke, dash: [5, 5] }); }); @@ -53,7 +54,7 @@ export function renderLinePaths( } withContext(context, (ctx) => { - ctx.translate(transformX, 0); + ctx.translate(transform.x, transform.y); linePaths.forEach((path) => { renderPathStroke(ctx, path, stroke); }); @@ -63,7 +64,7 @@ export function renderLinePaths( /** @internal */ export function renderAreaPath( ctx: CanvasRenderingContext2D, - transformX: number, + transform: Point, area: string, fill: Fill, clippedRanges: ClippedRanges, @@ -72,14 +73,14 @@ export function renderAreaPath( ) { if (clippedRanges.length > 0) { withClipRanges(ctx, clippedRanges, clippings, false, (ctx) => { - ctx.translate(transformX, 0); + ctx.translate(transform.x, transform.y); renderPathFill(ctx, area, fill); }); if (hideClippedRanges) { return; } withClipRanges(ctx, clippedRanges, clippings, true, (ctx) => { - ctx.translate(transformX, 0); + ctx.translate(transform.x, transform.y); const { opacity } = fill.color; const color = { ...fill.color, @@ -90,7 +91,7 @@ export function renderAreaPath( return; } withContext(ctx, (ctx) => { - ctx.translate(transformX, 0); + ctx.translate(transform.x, transform.y); renderPathFill(ctx, area, fill); }); } diff --git a/src/chart_types/xy_chart/renderer/canvas/renderers.ts b/src/chart_types/xy_chart/renderer/canvas/renderers.ts index 03556e2404..071cc583b0 100644 --- a/src/chart_types/xy_chart/renderer/canvas/renderers.ts +++ b/src/chart_types/xy_chart/renderer/canvas/renderers.ts @@ -27,6 +27,7 @@ import { renderBars } from './bars'; import { renderBubbles } from './bubbles'; import { renderGrids } from './grids'; import { renderLines } from './lines'; +import { renderGridPanels } from './panels/panels'; import { renderDebugRect } from './utils/debug'; import { renderBarValues } from './values/bar'; import { ReactiveChartStateProps } from './xy_chart'; @@ -42,25 +43,25 @@ export function renderXYChartCanvas2d( // let's set the devicePixelRatio once and for all; then we'll never worry about it again ctx.scale(dpr, dpr); const { - chartDimensions, + renderingArea, chartTransform, - chartRotation, + rotation, geometries, geometriesIndex, - theme, + theme: { axes: sharedAxesStyle, sharedStyle, barSeriesStyle }, highlightedLegendItem, annotationDimensions, annotationSpecs, - axisTickPositions, + perPanelAxisGeoms, + perPanelGridLines, axesSpecs, - axesTicksDimensions, axesStyles, - axesGridLinesPositions, debug, + panelGeoms, } = props; const transform = { - x: chartDimensions.left + chartTransform.x, - y: chartDimensions.top + chartTransform.y, + x: renderingArea.left + chartTransform.x, + y: renderingArea.top + chartTransform.y, }; // painter's algorithm, like that of SVG: the sequence determines what overdraws what; first element of the array is drawn first // (of course, with SVG, it's for ambiguous situations only, eg. when 3D transforms with different Z values aren't used, but @@ -69,36 +70,39 @@ export function renderXYChartCanvas2d( renderLayers(ctx, [ // clear the canvas (ctx: CanvasRenderingContext2D) => clearCanvas(ctx, 200000, 200000), - + // render panel grid + (ctx: CanvasRenderingContext2D) => { + if (debug) { + renderGridPanels(ctx, transform, panelGeoms); + } + }, (ctx: CanvasRenderingContext2D) => { renderAxes(ctx, { - axesPositions: axisTickPositions.axisPositions, axesSpecs, - axesTicksDimensions, - axesVisibleTicks: axisTickPositions.axisVisibleTicks, - chartDimensions, + perPanelAxisGeoms, + renderingArea, debug, axesStyles, - sharedAxesStyle: theme.axes, + sharedAxesStyle, }); }, (ctx: CanvasRenderingContext2D) => { renderGrids(ctx, { axesSpecs, - chartDimensions, - axesGridLinesPositions, + renderingArea, + perPanelGridLines, axesStyles, - sharedAxesStyle: theme.axes, + sharedAxesStyle, }); }, // rendering background annotations (ctx: CanvasRenderingContext2D) => { withContext(ctx, (ctx) => { - ctx.translate(transform.x, transform.y); - ctx.rotate((chartRotation * Math.PI) / 180); renderAnnotations( ctx, { + rotation, + renderingArea, annotationDimensions, annotationSpecs, }, @@ -110,73 +114,70 @@ export function renderXYChartCanvas2d( // rendering bars (ctx: CanvasRenderingContext2D) => { withContext(ctx, (ctx) => { - ctx.translate(transform.x, transform.y); - ctx.rotate((chartRotation * Math.PI) / 180); - renderBars(ctx, geometries.bars, theme.sharedStyle, clippings, highlightedLegendItem); + renderBars(ctx, geometries.bars, sharedStyle, clippings, renderingArea, highlightedLegendItem, rotation); }); }, // rendering areas (ctx: CanvasRenderingContext2D) => { withContext(ctx, (ctx) => { - ctx.translate(transform.x, transform.y); - ctx.rotate((chartRotation * Math.PI) / 180); renderAreas(ctx, { areas: geometries.areas, clippings, - highlightedLegendItem: highlightedLegendItem || null, - sharedStyle: theme.sharedStyle, + renderingArea, + rotation, + highlightedLegendItem, + sharedStyle, }); }); }, // rendering lines (ctx: CanvasRenderingContext2D) => { withContext(ctx, (ctx) => { - ctx.translate(transform.x, transform.y); - ctx.rotate((chartRotation * Math.PI) / 180); renderLines(ctx, { lines: geometries.lines, clippings, - highlightedLegendItem: highlightedLegendItem || null, - sharedStyle: theme.sharedStyle, + renderingArea, + rotation, + highlightedLegendItem, + sharedStyle, }); }); }, // rendering bubbles (ctx: CanvasRenderingContext2D) => { - withContext(ctx, (ctx) => { - ctx.translate(transform.x, transform.y); - ctx.rotate((chartRotation * Math.PI) / 180); - renderBubbles(ctx, { - bubbles: geometries.bubbles, - clippings, - highlightedLegendItem: highlightedLegendItem || null, - sharedStyle: theme.sharedStyle, - }); + renderBubbles(ctx, { + bubbles: geometries.bubbles, + clippings, + highlightedLegendItem, + sharedStyle, + rotation, + renderingArea, }); }, (ctx: CanvasRenderingContext2D) => { - withContext(ctx, (ctx) => { - ctx.translate(transform.x, transform.y); - ctx.rotate((chartRotation * Math.PI) / 180); - renderBarValues(ctx, { - bars: geometries.bars, - chartDimensions, - chartRotation, - debug, - theme, + geometries.bars.forEach(({ value: bars, panel }) => { + withContext(ctx, (ctx) => { + renderBarValues(ctx, { + bars, + panel, + renderingArea, + rotation, + debug, + barSeriesStyle, + }); }); }); }, // rendering foreground annotations (ctx: CanvasRenderingContext2D) => { withContext(ctx, (ctx) => { - ctx.translate(transform.x, transform.y); - ctx.rotate((chartRotation * Math.PI) / 180); renderAnnotations( ctx, { annotationDimensions, annotationSpecs, + rotation, + renderingArea, }, false, ); @@ -188,7 +189,7 @@ export function renderXYChartCanvas2d( return; } withContext(ctx, (ctx) => { - const { left, top, width, height } = chartDimensions; + const { left, top, width, height } = renderingArea; renderDebugRect( ctx, diff --git a/src/chart_types/xy_chart/renderer/canvas/utils/panel_transform.ts b/src/chart_types/xy_chart/renderer/canvas/utils/panel_transform.ts new file mode 100644 index 0000000000..06182b0232 --- /dev/null +++ b/src/chart_types/xy_chart/renderer/canvas/utils/panel_transform.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Rect } from '../../../../../geoms/types'; +import { withContext } from '../../../../../renderers/canvas'; +import { Rotation } from '../../../../../utils/commons'; +import { Dimensions } from '../../../../../utils/dimensions'; +import { computeChartTransform } from '../../../state/utils/utils'; + +export function withPanelTransform( + context: CanvasRenderingContext2D, + panel: Dimensions, + rotation: Rotation, + renderingArea: Dimensions, + fn: (ctx: CanvasRenderingContext2D) => void, + clippings?: { + area: Rect; + shouldClip?: boolean; + }, +) { + const transform = computeChartTransform(panel, rotation); + const left = renderingArea.left + panel.left + transform.x; + const top = renderingArea.top + panel.top + transform.y; + withContext(context, (ctx) => { + ctx.translate(left, top); + ctx.rotate((rotation * Math.PI) / 180); + + if (clippings?.shouldClip) { + const { x, y, width, height } = clippings.area; + ctx.save(); + ctx.beginPath(); + ctx.rect(x, y, width, height); + ctx.clip(); + } + fn(ctx); + if (clippings?.shouldClip) { + ctx.restore(); + } + }); +} diff --git a/src/chart_types/xy_chart/renderer/canvas/values/bar.ts b/src/chart_types/xy_chart/renderer/canvas/values/bar.ts index 561eed7d15..cb41d9930b 100644 --- a/src/chart_types/xy_chart/renderer/canvas/values/bar.ts +++ b/src/chart_types/xy_chart/renderer/canvas/values/bar.ts @@ -28,13 +28,15 @@ import { colorIsDark, getTextColorIfTextInvertible } from '../../../../partition import { getFillTextColor } from '../../../../partition_chart/layout/viewmodel/fill_text_layout'; import { renderText, wrapLines } from '../primitives/text'; import { renderDebugRect } from '../utils/debug'; +import { withPanelTransform } from '../utils/panel_transform'; interface BarValuesProps { - theme: Theme; - chartDimensions: Dimensions; - chartRotation: Rotation; + barSeriesStyle: Theme['barSeriesStyle']; + renderingArea: Dimensions; + rotation: Rotation; debug: boolean; bars: BarGeometry[]; + panel: Dimensions; } const CHART_DIRECTION: Record = { @@ -46,8 +48,8 @@ const CHART_DIRECTION: Record = { /** @internal */ export function renderBarValues(ctx: CanvasRenderingContext2D, props: BarValuesProps) { - const { bars, debug, chartRotation, chartDimensions, theme } = props; - const { fontFamily, fontStyle, fill, alignment } = theme.barSeriesStyle.displayValue; + const { bars, debug, rotation, renderingArea, barSeriesStyle, panel } = props; + const { fontFamily, fontStyle, fill, alignment } = barSeriesStyle.displayValue; const barsLength = bars.length; for (let i = 0; i < barsLength; i++) { const { displayValue } = bars[i]; @@ -72,16 +74,16 @@ export function renderBarValues(ctx: CanvasRenderingContext2D, props: BarValuesP const { x, y, align, baseline, rect } = positionText( bars[i], displayValue, - chartRotation, - theme.barSeriesStyle.displayValue, + rotation, + barSeriesStyle.displayValue, alignment, ); if (displayValue.isValueContainedInElement) { - const width = chartRotation === 0 || chartRotation === 180 ? bars[i].width : bars[i].height; + const width = rotation === 0 || rotation === 180 ? bars[i].width : bars[i].height; textLines = wrapLines(ctx, textLines.lines[0], font, fontSize, width, 100); } - if (displayValue.hideClippedValue && isOverflow(rect, chartDimensions, chartRotation)) { + if (displayValue.hideClippedValue && isOverflow(rect, renderingArea, rotation)) { continue; } if (debug) { @@ -94,24 +96,26 @@ export function renderBarValues(ctx: CanvasRenderingContext2D, props: BarValuesP for (let j = 0; j < linesLength; j++) { const textLine = textLines.lines[j]; - const origin = repositionTextLine({ x, y }, chartRotation, j, linesLength, { height, width }); - renderText( - ctx, - origin, - textLine, - { - ...font, - fill: fillColor, - fontSize, - align, - baseline, - shadow: shadowColor, - shadowSize, - }, - -chartRotation, - undefined, - fontScale, - ); + const origin = repositionTextLine({ x, y }, rotation, j, linesLength, { height, width }); + withPanelTransform(ctx, panel, rotation, renderingArea, (ctx) => { + renderText( + ctx, + origin, + textLine, + { + ...font, + fill: fillColor, + fontSize, + align, + baseline, + shadow: shadowColor, + shadowSize, + }, + -rotation, + undefined, + fontScale, + ); + }); } } } diff --git a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx index 55a7328d29..e10783d74a 100644 --- a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx +++ b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx @@ -37,17 +37,21 @@ import { LIGHT_THEME } from '../../../../utils/themes/light_theme'; import { Theme, AxisStyle } from '../../../../utils/themes/theme'; import { AnnotationDimensions } from '../../annotations/types'; import { computeAnnotationDimensionsSelector } from '../../state/selectors/compute_annotations'; -import { computeAxisTicksDimensionsSelector } from '../../state/selectors/compute_axis_ticks_dimensions'; -import { AxisVisibleTicks, computeAxisVisibleTicksSelector } from '../../state/selectors/compute_axis_visible_ticks'; import { computeChartDimensionsSelector } from '../../state/selectors/compute_chart_dimensions'; import { computeChartTransformSelector } from '../../state/selectors/compute_chart_transform'; +import { computePerPanelGridLinesSelector } from '../../state/selectors/compute_grid_lines'; +import { computePanelsSelectors, PanelGeoms } from '../../state/selectors/compute_panels'; +import { + computePerPanelAxesGeomsSelector, + PerPanelAxisGeoms, +} from '../../state/selectors/compute_per_panel_axes_geoms'; import { computeSeriesGeometriesSelector } from '../../state/selectors/compute_series_geometries'; import { getAxesStylesSelector } from '../../state/selectors/get_axis_styles'; import { getHighlightedSeriesSelector } from '../../state/selectors/get_highlighted_series'; import { getAnnotationSpecsSelector, getAxisSpecsSelector } from '../../state/selectors/get_specs'; import { isChartEmptySelector } from '../../state/selectors/is_chart_empty'; import { Geometries, Transform } from '../../state/utils/types'; -import { AxisLinePosition, AxisTicksDimensions } from '../../utils/axis_utils'; +import { LinesGrid } from '../../utils/grid_lines'; import { IndexedGeometryMap } from '../../utils/indexed_geometry_map'; import { AxisSpec, AnnotationSpec } from '../../utils/specs'; import { renderXYChartCanvas2d } from './renderers'; @@ -61,17 +65,17 @@ export interface ReactiveChartStateProps { geometriesIndex: IndexedGeometryMap; theme: Theme; chartContainerDimensions: Dimensions; - chartRotation: Rotation; - chartDimensions: Dimensions; + rotation: Rotation; + renderingArea: Dimensions; chartTransform: Transform; highlightedLegendItem?: LegendItem; axesSpecs: AxisSpec[]; - axesTicksDimensions: Map; + perPanelAxisGeoms: Array; + perPanelGridLines: Array; axesStyles: Map; - axisTickPositions: AxisVisibleTicks; - axesGridLinesPositions: Map; annotationDimensions: Map; annotationSpecs: AnnotationSpec[]; + panelGeoms: PanelGeoms; } interface ReactiveChartDispatchProps { @@ -123,12 +127,12 @@ class XYChartComponent extends React.Component { private drawCanvas() { if (this.ctx) { - const { chartDimensions, chartRotation } = this.props; + const { renderingArea, rotation } = this.props; const clippings = { x: 0, y: 0, - width: [90, -90].includes(chartRotation) ? chartDimensions.height : chartDimensions.width, - height: [90, -90].includes(chartRotation) ? chartDimensions.width : chartDimensions.height, + width: [90, -90].includes(rotation) ? renderingArea.height : renderingArea.width, + height: [90, -90].includes(rotation) ? renderingArea.width : renderingArea.height, }; renderXYChartCanvas2d(this.ctx, this.devicePixelRatio, clippings, this.props); } @@ -194,8 +198,8 @@ const DEFAULT_PROPS: ReactiveChartStateProps = { left: 0, top: 0, }, - chartRotation: 0 as const, - chartDimensions: { + rotation: 0 as const, + renderingArea: { width: 0, height: 0, left: 0, @@ -208,17 +212,12 @@ const DEFAULT_PROPS: ReactiveChartStateProps = { }, axesSpecs: [], - axisTickPositions: { - axisGridLinesPositions: new Map(), - axisPositions: new Map(), - axisTicks: new Map(), - axisVisibleTicks: new Map(), - }, - axesTicksDimensions: new Map(), + perPanelAxisGeoms: [], + perPanelGridLines: [], axesStyles: new Map(), - axesGridLinesPositions: new Map(), annotationDimensions: new Map(), annotationSpecs: [], + panelGeoms: [], }; const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { @@ -237,16 +236,16 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { theme: getChartThemeSelector(state), chartContainerDimensions: getChartContainerDimensionsSelector(state), highlightedLegendItem: getHighlightedSeriesSelector(state), - chartRotation: getChartRotationSelector(state), - chartDimensions: computeChartDimensionsSelector(state).chartDimensions, + rotation: getChartRotationSelector(state), + renderingArea: computeChartDimensionsSelector(state).chartDimensions, chartTransform: computeChartTransformSelector(state), axesSpecs: getAxisSpecsSelector(state), - axisTickPositions: computeAxisVisibleTicksSelector(state), - axesTicksDimensions: computeAxisTicksDimensionsSelector(state), + perPanelAxisGeoms: computePerPanelAxesGeomsSelector(state), + perPanelGridLines: computePerPanelGridLinesSelector(state), axesStyles: getAxesStylesSelector(state), - axesGridLinesPositions: computeAxisVisibleTicksSelector(state).axisGridLinesPositions, annotationDimensions: computeAnnotationDimensionsSelector(state), annotationSpecs: getAnnotationSpecsSelector(state), + panelGeoms: computePanelsSelectors(state), }; }; diff --git a/src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx b/src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx index 2fb369842c..135780d5f0 100644 --- a/src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx +++ b/src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx @@ -64,16 +64,15 @@ function renderAnnotationLineMarkers( annotationLines: AnnotationLineProps[], id: AnnotationId, ) { - return annotationLines.reduce((markers, { marker }: AnnotationLineProps, index: number) => { + return annotationLines.reduce((markers, { marker, panel }: AnnotationLineProps, index: number) => { if (!marker) { return markers; } - const { icon, color, position } = marker; const style = { color, - top: chartDimensions.top + position.top, - left: chartDimensions.left + position.left, + top: chartDimensions.top + position.top + panel.top, + left: chartDimensions.left + position.left + panel.left, }; markers.push( diff --git a/src/chart_types/xy_chart/renderer/dom/highlighter.tsx b/src/chart_types/xy_chart/renderer/dom/highlighter.tsx index c3f749342b..d3bdb54f1b 100644 --- a/src/chart_types/xy_chart/renderer/dom/highlighter.tsx +++ b/src/chart_types/xy_chart/renderer/dom/highlighter.tsx @@ -31,6 +31,7 @@ import { computeChartDimensionsSelector } from '../../state/selectors/compute_ch import { computeChartTransformSelector } from '../../state/selectors/compute_chart_transform'; import { getHighlightedGeomsSelector } from '../../state/selectors/get_tooltip_values_highlighted_geoms'; import { Transform } from '../../state/utils/types'; +import { computeChartTransform } from '../../state/utils/utils'; interface HighlighterProps { initialized: boolean; @@ -41,13 +42,16 @@ interface HighlighterProps { chartRotation: Rotation; } +function getTransformForPanel(panel: Dimensions, rotation: Rotation, { left, top }: Dimensions) { + const { x, y } = computeChartTransform(panel, rotation); + return `translate(${left + panel.left + x}, ${top + panel.top + y}) rotate(${rotation})`; +} + class HighlighterComponent extends React.Component { static displayName = 'Highlighter'; render() { - const { highlightedGeometries, chartTransform, chartDimensions, chartRotation, chartId } = this.props; - const left = chartDimensions.left + chartTransform.x; - const top = chartDimensions.top + chartTransform.y; + const { highlightedGeometries, chartDimensions, chartRotation, chartId } = this.props; const clipWidth = [90, -90].includes(chartRotation) ? chartDimensions.height : chartDimensions.width; const clipHeight = [90, -90].includes(chartRotation) ? chartDimensions.width : chartDimensions.height; const clipPathId = `echHighlighterClipPath__${chartId}`; @@ -58,18 +62,23 @@ class HighlighterComponent extends React.Component { - + {highlightedGeometries.map((geom, i) => { - const { color, x, y } = geom; + const { color, panel } = geom; + const geomTransform = getTransformForPanel(panel, chartRotation, chartDimensions); + const x = geom.x + geom.transform.x; + const y = geom.y + geom.transform.y; + if (isPointGeometry(geom)) { return ( @@ -82,6 +91,7 @@ class HighlighterComponent extends React.Component { y={y} width={geom.width} height={geom.height} + transform={geomTransform} className="echHighlighterOverlay__fill" clipPath={`url(#${clipPathId})`} /> diff --git a/src/chart_types/xy_chart/rendering/rendering.areas.test.ts b/src/chart_types/xy_chart/rendering/rendering.areas.test.ts index d7e74ce6ec..642f30b58a 100644 --- a/src/chart_types/xy_chart/rendering/rendering.areas.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.areas.test.ts @@ -17,132 +17,85 @@ * under the License. */ -import { ChartTypes } from '../..'; -import { MockSeriesSpec } from '../../../mocks/specs'; +import { Store } from 'redux'; + +import { MockPointGeometry } from '../../../mocks/geometries'; +import { MockSeriesIdentifier } from '../../../mocks/series/series_identifiers'; +import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs'; +import { MockStore } from '../../../mocks/store'; import { ScaleType } from '../../../scales/constants'; -import { SpecTypes } from '../../../specs/constants'; -import { CurveType } from '../../../utils/curves'; +import { Spec } from '../../../specs'; +import { GlobalChartState } from '../../../state/chart_state'; import { PointGeometry, AreaGeometry } from '../../../utils/geometry'; -import { LIGHT_THEME } from '../../../utils/themes/light_theme'; -import { computeSeriesDomains } from '../state/utils/utils'; +import { computeSeriesDomainsSelector } from '../state/selectors/compute_series_domains'; +import { computeSeriesGeometriesSelector } from '../state/selectors/compute_series_geometries'; +import { ComputedGeometries } from '../state/utils/types'; import { IndexedGeometryMap } from '../utils/indexed_geometry_map'; -import { computeXScale, computeYScales } from '../utils/scales'; -import { AreaSeriesSpec, SeriesTypes, StackMode } from '../utils/specs'; -import { renderArea } from './rendering'; +import { AreaSeriesSpec, StackMode } from '../utils/specs'; const SPEC_ID = 'spec_1'; const GROUP_ID = 'group_1'; -describe('Rendering points - areas', () => { - describe('Empty line for missing data', () => { - const pointSeriesSpec: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Area, - data: [ - [0, 10], - [1, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ - yDomains: pointSeriesDomains.yDomain, - range: [100, 0], - }); - let renderedArea: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; +function initStore(specs: Spec[], vizColors: string[] = ['red'], width = 100): Store { + const store = MockStore.default({ width, height: 100, top: 0, left: 0 }); + MockStore.addSpecs( + [ + ...specs, + MockGlobalSpec.settingsNoMargins({ + theme: { + colors: { + vizColors, + }, + }, + }), + ], + store, + ); + return store; +} - beforeEach(() => { - renderedArea = renderArea( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - { ...pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], data: [] }, - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, - ); - }); - test('Render geometry but empty upper and lower lines and area paths', () => { - const { - areaGeometry: { lines, area, color, seriesIdentifier, transform }, - } = renderedArea; - expect(lines.length).toBe(0); - expect(area).toBe(''); - expect(color).toBe('red'); - expect(seriesIdentifier.seriesKeys).toEqual([1]); - expect(seriesIdentifier.specId).toEqual(SPEC_ID); - expect(transform).toEqual({ x: 25, y: 0 }); - }); +describe('Rendering points - areas', () => { + test('Missing geometry if no data', () => { + const store = initStore([ + MockSeriesSpec.area({ + id: SPEC_ID, + groupId: GROUP_ID, + xScaleType: ScaleType.Ordinal, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], + data: [], + }), + ]); + const { + geometries: { areas }, + } = computeSeriesGeometriesSelector(store.getState()); + expect(areas).toHaveLength(0); }); describe('Single series area chart - ordinal', () => { - const pointSeriesSpec: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Area, - data: [ - [0, 10], - [1, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedArea: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - + let areaGeometry: AreaGeometry; + let geometriesIndex: IndexedGeometryMap; beforeEach(() => { - renderedArea = renderArea( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, - ); + const store = initStore([ + MockSeriesSpec.area({ + id: SPEC_ID, + groupId: GROUP_ID, + xScaleType: ScaleType.Ordinal, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], + data: [ + [0, 10], + [1, 5], + ], + }), + ]); + const geometries = computeSeriesGeometriesSelector(store.getState()); + [{ value: areaGeometry }] = geometries.geometries.areas; + geometriesIndex = geometries.geometriesIndex; }); test('Can render an line and area paths', () => { - const { - areaGeometry: { lines, area, color, seriesIdentifier, transform }, - } = renderedArea; + const { lines, area, color, seriesIdentifier, transform } = areaGeometry; expect(lines[0]).toBe('M0,0L50,50'); expect(area).toBe('M0,0L50,50L50,100L0,100Z'); expect(color).toBe('red'); @@ -152,11 +105,7 @@ describe('Rendering points - areas', () => { }); test('Can render two points', () => { - const { - areaGeometry: { points }, - indexedGeometryMap, - } = renderedArea; - + const { points } = areaGeometry; expect(points[0]).toEqual(({ x: 0, y: 0, @@ -167,7 +116,10 @@ describe('Rendering points - areas', () => { yAccessor: 1, splitAccessors: new Map(), seriesKeys: [1], - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', + key: + 'groupId{group_1}spec{spec_1}yAccessor{1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', + smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', + smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', }, styleOverrides: undefined, value: { @@ -181,6 +133,12 @@ describe('Rendering points - areas', () => { x: 25, y: 0, }, + panel: { + width: 100, + height: 100, + top: 0, + left: 0, + }, } as unknown) as PointGeometry); expect(points[1]).toEqual(({ x: 50, @@ -192,7 +150,10 @@ describe('Rendering points - areas', () => { yAccessor: 1, splitAccessors: new Map(), seriesKeys: [1], - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', + key: + 'groupId{group_1}spec{spec_1}yAccessor{1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', + smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', + smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', }, styleOverrides: undefined, value: { @@ -206,124 +167,86 @@ describe('Rendering points - areas', () => { x: 25, y: 0, }, + panel: { + width: 100, + height: 100, + top: 0, + left: 0, + }, } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + expect(geometriesIndex.size).toEqual(points.length); }); }); describe('Multi series area chart - ordinal', () => { - const spec1Id = 'spec_1'; - const spec2Id = 'spec_2'; - const pointSeriesSpec1: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: spec1Id, - groupId: GROUP_ID, - seriesType: SeriesTypes.Area, - data: [ - [0, 10], - [1, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const pointSeriesSpec2: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: spec2Id, - groupId: GROUP_ID, - seriesType: SeriesTypes.Area, - data: [ - [0, 20], - [1, 10], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec1, pointSeriesSpec2]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - - let firstLine: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - let secondLine: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - + let geometries: ComputedGeometries; beforeEach(() => { - firstLine = renderArea( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, - ); - secondLine = renderArea( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], - xScale, - yScales.get(GROUP_ID)!, - 'blue', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, + const store = initStore( + [ + MockSeriesSpec.area({ + id: 'spec_1', + groupId: GROUP_ID, + xScaleType: ScaleType.Ordinal, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], + data: [ + [0, 10], + [1, 5], + ], + }), + MockSeriesSpec.area({ + id: 'spec_2', + groupId: GROUP_ID, + xScaleType: ScaleType.Ordinal, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], + data: [ + [0, 20], + [1, 10], + ], + }), + ], + ['red', 'blue'], ); + geometries = computeSeriesGeometriesSelector(store.getState()); }); test('Can render two ordinal areas', () => { - expect(firstLine.areaGeometry.lines[0]).toBe('M0,50L50,75'); - expect(firstLine.areaGeometry.area).toBe('M0,50L50,75L50,100L0,100Z'); - expect(firstLine.areaGeometry.color).toBe('red'); - expect(firstLine.areaGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(firstLine.areaGeometry.seriesIdentifier.specId).toEqual(spec1Id); - expect(firstLine.areaGeometry.transform).toEqual({ x: 25, y: 0 }); + const { areas } = geometries.geometries; + const [{ value: firstArea }, { value: secondArea }] = areas; + expect(firstArea.lines[0]).toBe('M0,50L50,75'); + expect(firstArea.area).toBe('M0,50L50,75L50,100L0,100Z'); + expect(firstArea.color).toBe('red'); + expect(firstArea.seriesIdentifier.seriesKeys).toEqual([1]); + expect(firstArea.seriesIdentifier.specId).toEqual('spec_1'); + expect(firstArea.transform).toEqual({ x: 25, y: 0 }); - expect(secondLine.areaGeometry.lines[0]).toBe('M0,0L50,50'); - expect(secondLine.areaGeometry.area).toBe('M0,0L50,50L50,100L0,100Z'); - expect(secondLine.areaGeometry.color).toBe('blue'); - expect(secondLine.areaGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(secondLine.areaGeometry.seriesIdentifier.specId).toEqual(spec2Id); - expect(secondLine.areaGeometry.transform).toEqual({ x: 25, y: 0 }); + expect(secondArea.lines[0]).toBe('M0,0L50,50'); + expect(secondArea.area).toBe('M0,0L50,50L50,100L0,100Z'); + expect(secondArea.color).toBe('blue'); + expect(secondArea.seriesIdentifier.seriesKeys).toEqual([1]); + expect(secondArea.seriesIdentifier.specId).toEqual('spec_2'); + expect(secondArea.transform).toEqual({ x: 25, y: 0 }); }); test('can render first spec points', () => { - const { - areaGeometry: { points }, - indexedGeometryMap, - } = firstLine; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ + const { areas } = geometries.geometries; + const [{ value: firstArea }] = areas; + expect(firstArea.points.length).toEqual(2); + expect(firstArea.points[0]).toEqual(({ x: 0, y: 50, radius: 0, color: 'red', seriesIdentifier: { - specId: spec1Id, + specId: 'spec_1', yAccessor: 1, splitAccessors: new Map(), seriesKeys: [1], - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', + key: + 'groupId{group_1}spec{spec_1}yAccessor{1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', + smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', + smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', }, styleOverrides: undefined, value: { @@ -337,18 +260,27 @@ describe('Rendering points - areas', () => { x: 25, y: 0, }, + panel: { + width: 100, + height: 100, + top: 0, + left: 0, + }, } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ + expect(firstArea.points[1]).toEqual(({ x: 50, y: 75, radius: 0, color: 'red', seriesIdentifier: { - specId: spec1Id, + specId: 'spec_1', yAccessor: 1, splitAccessors: new Map(), seriesKeys: [1], - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', + key: + 'groupId{group_1}spec{spec_1}yAccessor{1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', + smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', + smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', }, styleOverrides: undefined, value: { @@ -362,26 +294,32 @@ describe('Rendering points - areas', () => { x: 25, y: 0, }, + panel: { + width: 100, + height: 100, + top: 0, + left: 0, + }, } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); }); test('can render second spec points', () => { - const { - areaGeometry: { points }, - indexedGeometryMap, - } = secondLine; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ + const { areas } = geometries.geometries; + const [, { value: secondArea }] = areas; + expect(secondArea.points.length).toEqual(2); + expect(secondArea.points[0]).toEqual(({ x: 0, y: 0, radius: 0, color: 'blue', seriesIdentifier: { - specId: spec2Id, + specId: 'spec_2', yAccessor: 1, splitAccessors: new Map(), seriesKeys: [1], - key: 'spec{spec_2}yAccessor{1}splitAccessors{}', + key: + 'groupId{group_1}spec{spec_2}yAccessor{1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', + smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', + smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', }, styleOverrides: undefined, value: { @@ -395,18 +333,27 @@ describe('Rendering points - areas', () => { x: 25, y: 0, }, + panel: { + width: 100, + height: 100, + top: 0, + left: 0, + }, } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ + expect(secondArea.points[1]).toEqual(({ x: 50, y: 50, radius: 0, color: 'blue', seriesIdentifier: { - specId: spec2Id, + specId: 'spec_2', yAccessor: 1, splitAccessors: new Map(), seriesKeys: [1], - key: 'spec{spec_2}yAccessor{1}splitAccessors{}', + key: + 'groupId{group_1}spec{spec_2}yAccessor{1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', + smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', + smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', }, styleOverrides: undefined, value: { @@ -420,652 +367,406 @@ describe('Rendering points - areas', () => { x: 25, y: 0, }, + panel: { + width: 100, + height: 100, + top: 0, + left: 0, + }, } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + }); + test('has the right number of geometry in the indexes', () => { + const { areas } = geometries.geometries; + const [{ value: firstArea }] = areas; + expect(geometries.geometriesIndex.size).toEqual(firstArea.points.length); }); }); + describe('Single series area chart - linear', () => { - const pointSeriesSpec: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, + let geometries: ComputedGeometries; + const spec = MockSeriesSpec.area({ + id: 'spec_1', groupId: GROUP_ID, - seriesType: SeriesTypes.Area, + xScaleType: ScaleType.Linear, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], data: [ [0, 10], [1, 5], ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Linear, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - - let renderedArea: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - beforeEach(() => { - renderedArea = renderArea( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, - ); + const store = initStore([spec], ['red']); + geometries = computeSeriesGeometriesSelector(store.getState()); }); + test('Can render a linear area', () => { - expect(renderedArea.areaGeometry.lines[0]).toBe('M0,0L100,50'); - expect(renderedArea.areaGeometry.area).toBe('M0,0L100,50L100,100L0,100Z'); - expect(renderedArea.areaGeometry.color).toBe('red'); - expect(renderedArea.areaGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(renderedArea.areaGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); - expect(renderedArea.areaGeometry.transform).toEqual({ x: 0, y: 0 }); + const { areas } = geometries.geometries; + const [{ value: firstArea }] = areas; + expect(firstArea.lines[0]).toBe('M0,0L100,50'); + expect(firstArea.area).toBe('M0,0L100,50L100,100L0,100Z'); + expect(firstArea.color).toBe('red'); + expect(firstArea.seriesIdentifier.seriesKeys).toEqual([1]); + expect(firstArea.seriesIdentifier.specId).toEqual(SPEC_ID); + expect(firstArea.transform).toEqual({ x: 0, y: 0 }); }); test('Can render two points', () => { - const { - areaGeometry: { points }, - indexedGeometryMap, - } = renderedArea; - expect(points[0]).toEqual(({ - x: 0, - y: 0, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 0, - y: 10, - mark: null, - datum: [0, 10], - }, - transform: { + const { areas } = geometries.geometries; + const [{ value: firstArea }] = areas; + expect(firstArea.points[0]).toEqual( + MockPointGeometry.default({ x: 0, y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 1, - y: 5, - mark: null, - datum: [1, 5], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec), + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + datum: [0, 10], + }, + }), + ); + expect(firstArea.points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec), + value: { + accessor: 'y1', + x: 1, + y: 5, + mark: null, + datum: [1, 5], + }, + }), + ); + expect(geometries.geometriesIndex.size).toEqual(firstArea.points.length); }); }); + describe('Multi series area chart - linear', () => { - const spec1Id = 'spec_1'; - const spec2Id = 'spec_2'; - const pointSeriesSpec1: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: spec1Id, + let geometries: ComputedGeometries; + const spec1 = MockSeriesSpec.area({ + id: 'spec_1', groupId: GROUP_ID, - seriesType: SeriesTypes.Area, + xScaleType: ScaleType.Linear, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], data: [ [0, 10], [1, 5], ], - xAccessor: 0, - yAccessors: [1], + }); + const spec2 = MockSeriesSpec.area({ + id: 'spec_2', + groupId: GROUP_ID, xScaleType: ScaleType.Linear, yScaleType: ScaleType.Linear, - }; - const pointSeriesSpec2: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: spec2Id, - groupId: GROUP_ID, - seriesType: SeriesTypes.Area, + xAccessor: 0, + yAccessors: [1], data: [ [0, 20], [1, 10], ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Linear, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec1, pointSeriesSpec2]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - - let firstLine: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - let secondLine: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - beforeEach(() => { - firstLine = renderArea( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, - ); - secondLine = renderArea( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], - xScale, - yScales.get(GROUP_ID)!, - 'blue', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, - ); + const store = initStore([spec1, spec2], ['red', 'blue']); + geometries = computeSeriesGeometriesSelector(store.getState()); }); test('can render two linear areas', () => { - expect(firstLine.areaGeometry.lines[0]).toBe('M0,50L100,75'); - expect(firstLine.areaGeometry.area).toBe('M0,50L100,75L100,100L0,100Z'); - expect(firstLine.areaGeometry.color).toBe('red'); - expect(firstLine.areaGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(firstLine.areaGeometry.seriesIdentifier.specId).toEqual(spec1Id); - expect(firstLine.areaGeometry.transform).toEqual({ x: 0, y: 0 }); + const { areas } = geometries.geometries; + const [{ value: firstArea }, { value: secondArea }] = areas; + expect(firstArea.lines[0]).toBe('M0,50L100,75'); + expect(firstArea.area).toBe('M0,50L100,75L100,100L0,100Z'); + expect(firstArea.color).toBe('red'); + expect(firstArea.seriesIdentifier.seriesKeys).toEqual([1]); + expect(firstArea.seriesIdentifier.specId).toEqual('spec_1'); + expect(firstArea.transform).toEqual({ x: 0, y: 0 }); - expect(secondLine.areaGeometry.lines[0]).toBe('M0,0L100,50'); - expect(secondLine.areaGeometry.area).toBe('M0,0L100,50L100,100L0,100Z'); - expect(secondLine.areaGeometry.color).toBe('blue'); - expect(secondLine.areaGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(secondLine.areaGeometry.seriesIdentifier.specId).toEqual(spec2Id); - expect(secondLine.areaGeometry.transform).toEqual({ x: 0, y: 0 }); + expect(secondArea.lines[0]).toBe('M0,0L100,50'); + expect(secondArea.area).toBe('M0,0L100,50L100,100L0,100Z'); + expect(secondArea.color).toBe('blue'); + expect(secondArea.seriesIdentifier.seriesKeys).toEqual([1]); + expect(secondArea.seriesIdentifier.specId).toEqual('spec_2'); + expect(secondArea.transform).toEqual({ x: 0, y: 0 }); }); test('can render first spec points', () => { - const { - areaGeometry: { points }, - indexedGeometryMap, - } = firstLine; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 0, - y: 10, - mark: null, - datum: [0, 10], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 75, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 1, - y: 5, - mark: null, - datum: [1, 5], - }, - transform: { + const { areas } = geometries.geometries; + const [{ value: firstArea }] = areas; + expect(firstArea.points.length).toEqual(2); + expect(firstArea.points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec1), + + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + datum: [0, 10], + }, + }), + ); + expect(firstArea.points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 75, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec1), + value: { + accessor: 'y1', + x: 1, + y: 5, + mark: null, + datum: [1, 5], + }, + }), + ); + expect(geometries.geometriesIndex.size).toEqual(firstArea.points.length); }); test('can render second spec points', () => { - const { - areaGeometry: { points }, - indexedGeometryMap, - } = secondLine; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 0, - radius: 0, - color: 'blue', - seriesIdentifier: { - specId: spec2Id, - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - key: 'spec{spec_2}yAccessor{1}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 0, - y: 20, - mark: null, - datum: [0, 20], - }, - transform: { + const { areas } = geometries.geometries; + const [, { value: secondArea }] = areas; + expect(secondArea.points.length).toEqual(2); + expect(secondArea.points[0]).toEqual( + MockPointGeometry.default({ x: 0, y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 50, - radius: 0, - color: 'blue', - seriesIdentifier: { - specId: spec2Id, - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - key: 'spec{spec_2}yAccessor{1}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 1, - y: 10, - mark: null, - datum: [1, 10], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + radius: 0, + color: 'blue', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec2), + + value: { + accessor: 'y1', + x: 0, + y: 20, + mark: null, + datum: [0, 20], + }, + }), + ); + expect(secondArea.points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 50, + radius: 0, + color: 'blue', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec2), + value: { + accessor: 'y1', + x: 1, + y: 10, + mark: null, + datum: [1, 10], + }, + }), + ); + expect(geometries.geometriesIndex.size).toEqual(secondArea.points.length); }); }); describe('Single series area chart - time', () => { - const pointSeriesSpec: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, + let geometries: ComputedGeometries; + const spec = MockSeriesSpec.area({ + id: 'spec_1', groupId: GROUP_ID, - seriesType: SeriesTypes.Area, + xScaleType: ScaleType.Time, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], data: [ [1546300800000, 10], [1546387200000, 5], ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Time, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - - let renderedArea: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - beforeEach(() => { - renderedArea = renderArea( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, - ); + const store = initStore([spec], ['red']); + geometries = computeSeriesGeometriesSelector(store.getState()); }); + test('Can render a time area', () => { - expect(renderedArea.areaGeometry.lines[0]).toBe('M0,0L100,50'); - expect(renderedArea.areaGeometry.area).toBe('M0,0L100,50L100,100L0,100Z'); - expect(renderedArea.areaGeometry.color).toBe('red'); - expect(renderedArea.areaGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(renderedArea.areaGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); - expect(renderedArea.areaGeometry.transform).toEqual({ x: 0, y: 0 }); + const { areas } = geometries.geometries; + const [{ value: firstArea }] = areas; + expect(firstArea.lines[0]).toBe('M0,0L100,50'); + expect(firstArea.area).toBe('M0,0L100,50L100,100L0,100Z'); + expect(firstArea.color).toBe('red'); + expect(firstArea.seriesIdentifier.seriesKeys).toEqual([1]); + expect(firstArea.seriesIdentifier.specId).toEqual(SPEC_ID); + expect(firstArea.transform).toEqual({ x: 0, y: 0 }); }); test('Can render two points', () => { - const { - areaGeometry: { points }, - indexedGeometryMap, - } = renderedArea; - expect(points[0]).toEqual(({ - x: 0, - y: 0, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 1546300800000, - y: 10, - mark: null, - datum: [1546300800000, 10], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 1546387200000, - y: 5, - mark: null, - datum: [1546387200000, 5], - }, - transform: { + const { areas } = geometries.geometries; + const [{ value: firstArea }] = areas; + expect(firstArea.points[0]).toEqual( + MockPointGeometry.default({ x: 0, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec), + + value: { + accessor: 'y1', + x: 1546300800000, + y: 10, + mark: null, + datum: [1546300800000, 10], + }, + }), + ); + expect(firstArea.points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec), + styleOverrides: undefined, + value: { + accessor: 'y1', + x: 1546387200000, + y: 5, + mark: null, + datum: [1546387200000, 5], + }, + }), + ); + expect(geometries.geometriesIndex.size).toEqual(firstArea.points.length); }); }); describe('Multi series area chart - time', () => { - const spec1Id = 'spec_1'; - const spec2Id = 'spec_2'; - const pointSeriesSpec1: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: spec1Id, + let geometries: ComputedGeometries; + const spec1 = MockSeriesSpec.area({ + id: 'spec_1', groupId: GROUP_ID, - seriesType: SeriesTypes.Area, + xScaleType: ScaleType.Time, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], data: [ [1546300800000, 10], [1546387200000, 5], ], - xAccessor: 0, - yAccessors: [1], + }); + const spec2 = MockSeriesSpec.area({ + id: 'spec_2', + groupId: GROUP_ID, xScaleType: ScaleType.Time, yScaleType: ScaleType.Linear, - }; - const pointSeriesSpec2: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: spec2Id, - groupId: GROUP_ID, - seriesType: SeriesTypes.Area, + xAccessor: 0, + yAccessors: [1], data: [ [1546300800000, 20], [1546387200000, 10], ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Time, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec1, pointSeriesSpec2]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - - let firstLine: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - let secondLine: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - beforeEach(() => { - firstLine = renderArea( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, - ); - secondLine = renderArea( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], - xScale, - yScales.get(GROUP_ID)!, - 'blue', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, - ); + const store = initStore([spec1, spec2], ['red', 'blue']); + geometries = computeSeriesGeometriesSelector(store.getState()); }); + test('can render first spec points', () => { - const { - areaGeometry: { points }, - indexedGeometryMap, - } = firstLine; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 1546300800000, - y: 10, - mark: null, - datum: [1546300800000, 10], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 75, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 1546387200000, - y: 5, - mark: null, - datum: [1546387200000, 5], - }, - transform: { + const { areas } = geometries.geometries; + const [{ value: firstArea }] = areas; + expect(firstArea.points.length).toEqual(2); + expect(firstArea.points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec1), + + value: { + accessor: 'y1', + x: 1546300800000, + y: 10, + mark: null, + datum: [1546300800000, 10], + }, + }), + ); + expect(firstArea.points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 75, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec1), + + value: { + accessor: 'y1', + x: 1546387200000, + y: 5, + mark: null, + datum: [1546387200000, 5], + }, + }), + ); + expect(geometries.geometriesIndex.size).toEqual(firstArea.points.length); }); test('can render second spec points', () => { - const { - areaGeometry: { points }, - indexedGeometryMap, - } = secondLine; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 0, - radius: 0, - color: 'blue', - seriesIdentifier: { - specId: spec2Id, - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - key: 'spec{spec_2}yAccessor{1}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 1546300800000, - y: 20, - mark: null, - datum: [1546300800000, 20], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 50, - radius: 0, - color: 'blue', - seriesIdentifier: { - specId: spec2Id, - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - key: 'spec{spec_2}yAccessor{1}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 1546387200000, - y: 10, - mark: null, - datum: [1546387200000, 10], - }, - transform: { + const { areas } = geometries.geometries; + const [, { value: secondArea }] = areas; + + expect(secondArea.points.length).toEqual(2); + expect(secondArea.points[0]).toEqual( + MockPointGeometry.default({ x: 0, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + radius: 0, + color: 'blue', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec2), + + value: { + accessor: 'y1', + x: 1546300800000, + y: 20, + mark: null, + datum: [1546300800000, 20], + }, + }), + ); + expect(secondArea.points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 50, + radius: 0, + color: 'blue', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec2), + + value: { + accessor: 'y1', + x: 1546387200000, + y: 10, + mark: null, + datum: [1546387200000, 10], + }, + }), + ); + expect(geometries.geometriesIndex.size).toEqual(secondArea.points.length); }); }); describe('Single series area chart - y log', () => { - const pointSeriesSpec: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, + let geometries: ComputedGeometries; + const spec = MockSeriesSpec.area({ + id: 'spec_1', groupId: GROUP_ID, - seriesType: SeriesTypes.Area, + xScaleType: ScaleType.Linear, + yScaleType: ScaleType.Log, + xAccessor: 0, + yAccessors: [1], data: [ [0, 10], [1, 5], @@ -1077,63 +778,40 @@ describe('Rendering points - areas', () => { [7, 10], [8, 10], ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Linear, - yScaleType: ScaleType.Log, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 90], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - - let renderedArea: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - beforeEach(() => { - renderedArea = renderArea( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, - ); + const store = initStore([spec], ['red'], 90); + geometries = computeSeriesGeometriesSelector(store.getState()); }); + test('Can render a splitted area and line', () => { - // expect(renderedArea.lineGeometry.line).toBe('ss'); - expect(renderedArea.areaGeometry.lines[0].split('M').length - 1).toBe(3); - expect(renderedArea.areaGeometry.area.split('M').length - 1).toBe(3); - expect(renderedArea.areaGeometry.color).toBe('red'); - expect(renderedArea.areaGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(renderedArea.areaGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); - expect(renderedArea.areaGeometry.transform).toEqual({ x: 0, y: 0 }); + const { areas } = geometries.geometries; + const [{ value: firstArea }] = areas; + expect(firstArea.lines[0].split('M').length - 1).toBe(3); + expect(firstArea.area.split('M').length - 1).toBe(3); + expect(firstArea.color).toBe('red'); + expect(firstArea.seriesIdentifier.seriesKeys).toEqual([1]); + expect(firstArea.seriesIdentifier.specId).toEqual(SPEC_ID); + expect(firstArea.transform).toEqual({ x: 0, y: 0 }); }); test('Can render points', () => { const { - areaGeometry: { points }, - indexedGeometryMap, - } = renderedArea; + geometriesIndex, + geometries: { areas }, + } = geometries; + const [ + { + value: { points }, + }, + ] = areas; // all the points minus the undefined ones on a log scale expect(points.length).toBe(7); // all the points expect null geometries - expect(indexedGeometryMap.size).toEqual(8); - const nullIndexdGeometry = indexedGeometryMap.find(2)!; + expect(geometriesIndex.size).toEqual(8); + const nullIndexdGeometry = geometriesIndex.find(2)!; expect(nullIndexdGeometry).toEqual([]); - const zeroValueIndexdGeometry = indexedGeometryMap.find(5)!; + const zeroValueIndexdGeometry = geometriesIndex.find(5)!; expect(zeroValueIndexdGeometry).toBeDefined(); expect(zeroValueIndexdGeometry.length).toBe(1); // moved to the bottom of the chart @@ -1169,8 +847,11 @@ describe('Rendering points - areas', () => { stackAccessors: [0], stackMode: StackMode.Percentage, }); - const pointSeriesDomains = computeSeriesDomains([pointSeriesSpec1, pointSeriesSpec2]); - expect(pointSeriesDomains.formattedDataSeries.stacked[0].dataSeries[0].data).toMatchObject([ + + const store = initStore([pointSeriesSpec1, pointSeriesSpec2]); + const domains = computeSeriesDomainsSelector(store.getState()); + + expect(domains.formattedDataSeries[0].data).toMatchObject([ { datum: [1546300800000, 0], initialY0: null, @@ -1216,8 +897,10 @@ describe('Rendering points - areas', () => { yScaleType: ScaleType.Linear, stackAccessors: [0], }); - const pointSeriesDomains = computeSeriesDomains([pointSeriesSpec1, pointSeriesSpec2]); - expect(pointSeriesDomains.formattedDataSeries.stacked[0].dataSeries[0].data).toMatchObject([ + const store = initStore([pointSeriesSpec1, pointSeriesSpec2]); + const domains = computeSeriesDomainsSelector(store.getState()); + + expect(domains.formattedDataSeries[0].data).toMatchObject([ { datum: [1546300800000, null], initialY0: null, @@ -1238,7 +921,7 @@ describe('Rendering points - areas', () => { }, ]); - expect(pointSeriesDomains.formattedDataSeries.stacked[0].dataSeries[1].data).toEqual([ + expect(domains.formattedDataSeries[1].data).toEqual([ { datum: [1546300800000, 3], initialY0: null, @@ -1260,90 +943,90 @@ describe('Rendering points - areas', () => { ]); }); - describe('Error guards for scaled values', () => { - const pointSeriesSpec: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Area, - data: [ - [0, 10], - [1, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedArea: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - renderedArea = renderArea( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, - ); - }); - - describe('xScale values throw error', () => { - beforeAll(() => { - jest.spyOn(xScale, 'scaleOrThrow').mockImplementation(() => { - throw new Error(); - }); - }); - - test('Should include no lines nor area', () => { - const { - areaGeometry: { lines, area, color, seriesIdentifier, transform }, - } = renderedArea; - expect(lines).toHaveLength(0); - expect(area).toBe(''); - expect(color).toBe('red'); - expect(seriesIdentifier.seriesKeys).toEqual([1]); - expect(seriesIdentifier.specId).toEqual(SPEC_ID); - expect(transform).toEqual({ x: 25, y: 0 }); - }); - }); - - describe('yScale values throw error', () => { - beforeAll(() => { - jest.spyOn(yScales.get(GROUP_ID)!, 'scaleOrThrow').mockImplementation(() => { - throw new Error(); - }); - }); - - test('Should include no lines nor area', () => { - const { - areaGeometry: { lines, area, color, seriesIdentifier, transform }, - } = renderedArea; - expect(lines).toHaveLength(0); - expect(area).toBe(''); - expect(color).toBe('red'); - expect(seriesIdentifier.seriesKeys).toEqual([1]); - expect(seriesIdentifier.specId).toEqual(SPEC_ID); - expect(transform).toEqual({ x: 25, y: 0 }); - }); - }); - }); + // describe('Error guards for scaled values', () => { + // const pointSeriesSpec: AreaSeriesSpec = { + // chartType: ChartTypes.XYAxis, + // specType: SpecTypes.Series, + // id: SPEC_ID, + // groupId: GROUP_ID, + // seriesType: SeriesTypes.Area, + // data: [ + // [0, 10], + // [1, 5], + // ], + // xAccessor: 0, + // yAccessors: [1], + // xScaleType: ScaleType.Ordinal, + // yScaleType: ScaleType.Linear, + // }; + // const pointSeriesMap = [pointSeriesSpec]; + // const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); + // const xScale = computeXScale({ + // xDomain: pointSeriesDomains.xDomain, + // totalBarsInCluster: pointSeriesMap.length, + // range: [0, 100], + // }); + // const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); + // let renderedArea: { + // areaGeometry: AreaGeometry; + // indexedGeometryMap: IndexedGeometryMap; + // }; + // + // beforeEach(() => { + // renderedArea = renderArea( + // 25, // adding a ideal 25px shift, generally applied by renderGeometries + // pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], + // xScale, + // yScales.get(GROUP_ID)!, + // 'red', + // CurveType.LINEAR, + // false, + // 0, + // LIGHT_THEME.areaSeriesStyle, + // { + // enabled: false, + // }, + // ); + // }); + // + // describe('xScale values throw error', () => { + // beforeAll(() => { + // jest.spyOn(xScale, 'scaleOrThrow').mockImplementation(() => { + // throw new Error(); + // }); + // }); + // + // test('Should include no lines nor area', () => { + // const { + // areaGeometry: { lines, area, color, seriesIdentifier, transform }, + // } = renderedArea; + // expect(lines).toHaveLength(0); + // expect(area).toBe(''); + // expect(color).toBe('red'); + // expect(seriesIdentifier.seriesKeys).toEqual([1]); + // expect(seriesIdentifier.specId).toEqual(SPEC_ID); + // expect(transform).toEqual({ x: 25, y: 0 }); + // }); + // }); + // + // describe('yScale values throw error', () => { + // beforeAll(() => { + // jest.spyOn(yScales.get(GROUP_ID)!, 'scaleOrThrow').mockImplementation(() => { + // throw new Error(); + // }); + // }); + // + // test('Should include no lines nor area', () => { + // const { + // areaGeometry: { lines, area, color, seriesIdentifier, transform }, + // } = renderedArea; + // expect(lines).toHaveLength(0); + // expect(area).toBe(''); + // expect(color).toBe('red'); + // expect(seriesIdentifier.seriesKeys).toEqual([1]); + // expect(seriesIdentifier.specId).toEqual(SPEC_ID); + // expect(transform).toEqual({ x: 25, y: 0 }); + // }); + // }); + // }); }); diff --git a/src/chart_types/xy_chart/rendering/rendering.bands.test.ts b/src/chart_types/xy_chart/rendering/rendering.bands.test.ts index 0134440e1a..7263c1b3d2 100644 --- a/src/chart_types/xy_chart/rendering/rendering.bands.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.bands.test.ts @@ -17,88 +17,20 @@ * under the License. */ -import { ChartTypes } from '../..'; -import { MockPointGeometry } from '../../../mocks'; +import { MockBarGeometry, MockPointGeometry } from '../../../mocks'; +import { MockSeriesIdentifier } from '../../../mocks/series/series_identifiers'; +import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs'; +import { MockStore } from '../../../mocks/store'; import { ScaleType } from '../../../scales/constants'; -import { SpecTypes } from '../../../specs/constants'; -import { CurveType } from '../../../utils/curves'; -import { AreaGeometry, PointGeometry } from '../../../utils/geometry'; -import { LIGHT_THEME } from '../../../utils/themes/light_theme'; -import { computeSeriesDomains } from '../state/utils/utils'; -import { IndexedGeometryMap } from '../utils/indexed_geometry_map'; -import { computeXScale, computeYScales } from '../utils/scales'; -import { AreaSeriesSpec, BarSeriesSpec, SeriesTypes } from '../utils/specs'; -import { renderArea, renderBars } from './rendering'; +import { computeSeriesGeometriesSelector } from '../state/selectors/compute_series_geometries'; const SPEC_ID = 'spec_1'; const GROUP_ID = 'group_1'; describe('Rendering bands - areas', () => { - describe('Empty line for missing data', () => { - const pointSeriesSpec: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Area, - data: [ - [0, 2, 10], - [1, 3, 5], - ], - xAccessor: 0, - y0Accessors: [1], - yAccessors: [2], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedArea: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - renderedArea = renderArea( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - { ...pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], data: [] }, - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - true, - 0, - LIGHT_THEME.areaSeriesStyle, - { - enabled: false, - }, - ); - }); - test('Render geometry but empty upper and lower lines and area paths', () => { - const { - areaGeometry: { lines, area, color, seriesIdentifier, transform }, - } = renderedArea; - expect(lines.length).toBe(0); - expect(area).toBe(''); - expect(color).toBe('red'); - expect(seriesIdentifier.seriesKeys).toEqual([2]); - expect(seriesIdentifier.specId).toEqual(SPEC_ID); - expect(transform).toEqual({ x: 25, y: 0 }); - }); - }); describe('Single band area chart', () => { - const pointSeriesSpec: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec = MockSeriesSpec.area({ id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Area, data: [ [0, 2, 10], [1, 3, 5], @@ -108,40 +40,20 @@ describe('Rendering bands - areas', () => { yAccessors: [2], xScaleType: ScaleType.Ordinal, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedArea: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec, settings], store); + const { + geometries: { areas }, + } = computeSeriesGeometriesSelector(store.getState()); - beforeEach(() => { - renderedArea = renderArea( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - true, - 0, - LIGHT_THEME.areaSeriesStyle, + test('Can render upper and lower lines and area paths', () => { + const [ { - enabled: false, + value: { lines, area, color, seriesIdentifier, transform }, }, - ); - }); - test('Can render upper and lower lines and area paths', () => { - const { - areaGeometry: { lines, area, color, seriesIdentifier, transform }, - } = renderedArea; + ] = areas; expect(lines.length).toBe(2); expect(lines[0]).toBe('M0,0L50,50'); expect(lines[1]).toBe('M0,80L50,70'); @@ -153,120 +65,103 @@ describe('Rendering bands - areas', () => { }); test('Can render two points', () => { - const { - areaGeometry: { points }, - } = renderedArea; - expect(points.length).toBe(4); - expect(points[0]).toEqual(({ - x: 0, - y: 80, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', + const [ + { + value: { points }, }, - styleOverrides: undefined, - value: { - accessor: 'y0', + ] = areas; + expect(points.length).toBe(4); + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 2, - mark: null, - datum: [0, 2, 10], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); + y: 80, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + styleOverrides: undefined, + value: { + accessor: 'y0', + x: 0, + y: 2, + mark: null, + datum: [0, 2, 10], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); - expect(points[1]).toEqual(({ - x: 0, - y: 0, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', + expect(points[1]).toEqual( + MockPointGeometry.default({ x: 0, - y: 10, - mark: null, - datum: [0, 2, 10], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[2]).toEqual(({ - x: 50, - y: 70, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', - }, - value: { - accessor: 'y0', - x: 1, - y: 3, - mark: null, - datum: [1, 3, 5], - }, - styleOverrides: undefined, - transform: { - x: 25, y: 0, - }, - } as unknown) as PointGeometry); - expect(points[3]).toEqual(({ - x: 50, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', - }, - styleOverrides: undefined, - value: { - accessor: 'y1', - x: 1, - y: 5, - mark: null, - datum: [1, 3, 5], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + styleOverrides: undefined, + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + datum: [0, 2, 10], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); + expect(points[2]).toEqual( + MockPointGeometry.default({ + x: 50, + y: 70, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y0', + x: 1, + y: 3, + mark: null, + datum: [1, 3, 5], + }, + styleOverrides: undefined, + transform: { + x: 25, + y: 0, + }, + }), + ); + expect(points[3]).toEqual( + MockPointGeometry.default({ + x: 50, + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + styleOverrides: undefined, + value: { + accessor: 'y1', + x: 1, + y: 5, + mark: null, + datum: [1, 3, 5], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); }); }); describe('Single band area chart with null values', () => { - const pointSeriesSpec: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec = MockSeriesSpec.area({ id: SPEC_ID, groupId: GROUP_ID, - seriesType: SeriesTypes.Area, data: [ [0, 2, 10], [1, 2, null], @@ -278,40 +173,20 @@ describe('Rendering bands - areas', () => { yAccessors: [2], xScaleType: ScaleType.Ordinal, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedArea: { - areaGeometry: AreaGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec, settings], store); + const { + geometries: { areas }, + } = computeSeriesGeometriesSelector(store.getState()); - beforeEach(() => { - renderedArea = renderArea( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - true, - 0, - LIGHT_THEME.areaSeriesStyle, + test('Can render upper and lower lines and area paths', () => { + const [ { - enabled: false, + value: { lines, area, color, seriesIdentifier, transform }, }, - ); - }); - test('Can render upper and lower lines and area paths', () => { - const { - areaGeometry: { lines, area, color, seriesIdentifier, transform }, - } = renderedArea; + ] = areas; expect(lines.length).toBe(2); expect(lines[0]).toBe('M0,0ZM50,50L75,50'); expect(lines[1]).toBe('M0,80ZM50,70L75,70'); @@ -319,13 +194,15 @@ describe('Rendering bands - areas', () => { expect(color).toBe('red'); expect(seriesIdentifier.seriesKeys).toEqual([2]); expect(seriesIdentifier.specId).toEqual(SPEC_ID); - expect(transform).toEqual({ x: 25, y: 0 }); + expect(transform).toEqual({ x: 12.5, y: 0 }); }); test('Can render two points', () => { - const { - areaGeometry: { points }, - } = renderedArea; + const [ + { + value: { points }, + }, + ] = areas; expect(points.length).toBe(6); const getPointGeo = MockPointGeometry.fromBaseline( { @@ -341,7 +218,7 @@ describe('Rendering bands - areas', () => { datum: [0, 2, 10], }, transform: { - x: 25, + x: 12.5, y: 0, }, }, @@ -426,12 +303,9 @@ describe('Rendering bands - areas', () => { }); }); describe('Single series band bar chart - ordinal', () => { - const barSeriesSpec: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const barSeriesSpec = MockSeriesSpec.bar({ id: SPEC_ID, groupId: GROUP_ID, - seriesType: SeriesTypes.Bar, data: [ [0, 2, 10], [1, 3, null], @@ -443,146 +317,126 @@ describe('Rendering bands - areas', () => { yAccessors: [2], xScaleType: ScaleType.Ordinal, yScaleType: ScaleType.Linear, - }; - const barSeriesMap = [barSeriesSpec]; - const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: barSeriesDomains.xDomain, - totalBarsInCluster: barSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([barSeriesSpec, settings], store); + const { + geometries: { + bars: [{ value: bars }], + }, + } = computeSeriesGeometriesSelector(store.getState()); test('Can render two bars', () => { - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - ); - expect(barGeometries.length).toBe(3); - expect(barGeometries[0]).toEqual({ - x: 0, - y: 0, - width: 25, - height: 80, - color: 'red', - value: { - accessor: 'y1', + expect(bars.length).toBe(3); + expect(bars[0]).toEqual( + MockBarGeometry.default({ x: 0, - y: 10, - mark: null, - datum: [0, 2, 10], - }, - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', - }, - displayValue: undefined, - seriesStyle: { - displayValue: { - fill: '#777', - fontFamily: 'sans-serif', - fontSize: 8, - fontStyle: 'normal', - offsetX: 0, - offsetY: 0, - padding: 0, - }, - rect: { - opacity: 1, - }, - rectBorder: { - strokeWidth: 0, - visible: false, - }, - }, - }); - expect(barGeometries[1]).toEqual({ - x: 50, - y: 50, - width: 25, - height: 20, - color: 'red', - value: { - accessor: 'y1', - x: 2, - y: 5, - mark: null, - datum: [2, 3, 5], - }, - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', - }, - displayValue: undefined, - seriesStyle: { - displayValue: { - fill: '#777', - fontFamily: 'sans-serif', - fontSize: 8, - fontStyle: 'normal', - offsetX: 0, - offsetY: 0, - padding: 0, + y: 0, + width: 25, + height: 80, + color: 'red', + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + datum: [0, 2, 10], }, - rect: { - opacity: 1, + seriesIdentifier: MockSeriesIdentifier.fromSpec(barSeriesSpec), + displayValue: undefined, + seriesStyle: { + displayValue: { + fill: '#777', + fontFamily: 'sans-serif', + fontSize: 8, + fontStyle: 'normal', + offsetX: 0, + offsetY: 0, + padding: 0, + }, + rect: { + opacity: 1, + }, + rectBorder: { + strokeWidth: 0, + visible: false, + }, }, - rectBorder: { - strokeWidth: 0, - visible: false, + }), + ); + expect(bars[1]).toEqual( + MockBarGeometry.default({ + x: 50, + y: 50, + width: 25, + height: 20, + color: 'red', + value: { + accessor: 'y1', + x: 2, + y: 5, + mark: null, + datum: [2, 3, 5], }, - }, - }); - expect(barGeometries[2]).toEqual({ - x: 75, - y: 20, - width: 25, - height: 40, - color: 'red', - value: { - accessor: 'y1', - x: 3, - y: 8, - mark: null, - datum: [3, 4, 8], - }, - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', - }, - displayValue: undefined, - seriesStyle: { - displayValue: { - fill: '#777', - fontFamily: 'sans-serif', - fontSize: 8, - fontStyle: 'normal', - offsetX: 0, - offsetY: 0, - padding: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(barSeriesSpec), + displayValue: undefined, + seriesStyle: { + displayValue: { + fill: '#777', + fontFamily: 'sans-serif', + fontSize: 8, + fontStyle: 'normal', + offsetX: 0, + offsetY: 0, + padding: 0, + }, + rect: { + opacity: 1, + }, + rectBorder: { + strokeWidth: 0, + visible: false, + }, }, - rect: { - opacity: 1, + }), + ); + expect(bars[2]).toEqual( + MockBarGeometry.default({ + x: 75, + y: 20, + width: 25, + height: 40, + color: 'red', + value: { + accessor: 'y1', + x: 3, + y: 8, + mark: null, + datum: [3, 4, 8], }, - rectBorder: { - strokeWidth: 0, - visible: false, + seriesIdentifier: MockSeriesIdentifier.fromSpec(barSeriesSpec), + displayValue: undefined, + seriesStyle: { + displayValue: { + fill: '#777', + fontFamily: 'sans-serif', + fontSize: 8, + fontStyle: 'normal', + offsetX: 0, + offsetY: 0, + padding: 0, + }, + rect: { + opacity: 1, + }, + rectBorder: { + strokeWidth: 0, + visible: false, + }, }, - }, - }); + }), + ); }); }); }); diff --git a/src/chart_types/xy_chart/rendering/rendering.bars.test.ts b/src/chart_types/xy_chart/rendering/rendering.bars.test.ts index 1d479f1a96..5cccbccc8e 100644 --- a/src/chart_types/xy_chart/rendering/rendering.bars.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.bars.test.ts @@ -17,196 +17,39 @@ * under the License. */ -import { ChartTypes } from '../..'; import { MockBarGeometry } from '../../../mocks'; +import { MockSeriesIdentifier } from '../../../mocks/series/series_identifiers'; +import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs'; +import { MockStore } from '../../../mocks/store'; import { ScaleType } from '../../../scales/constants'; -import { SpecTypes } from '../../../specs/constants'; import { identity } from '../../../utils/commons'; -import { GroupId } from '../../../utils/ids'; -import { LIGHT_THEME } from '../../../utils/themes/light_theme'; -import { computeSeriesDomains } from '../state/utils/utils'; -import { computeXScale, computeYScales } from '../utils/scales'; -import { BarSeriesSpec, DomainRange, SeriesTypes } from '../utils/specs'; -import { renderBars } from './rendering'; +import { computeSeriesGeometriesSelector } from '../state/selectors/compute_series_geometries'; const SPEC_ID = 'spec_1'; const GROUP_ID = 'group_1'; describe('Rendering bars', () => { - describe('Single series bar chart - ordinal', () => { - const barSeriesSpec: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + test('Can render two bars within domain', () => { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + const spec = MockSeriesSpec.bar({ id: SPEC_ID, groupId: GROUP_ID, - seriesType: SeriesTypes.Bar, + xScaleType: ScaleType.Ordinal, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], data: [ [-200, 0], [0, 10], [1, 5], ], // first datum should be skipped as it's out of domain - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const barSeriesMap = [barSeriesSpec]; - const customDomain = [0, 1]; - const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map(), [], customDomain); - const xScale = computeXScale({ - xDomain: barSeriesDomains.xDomain, - totalBarsInCluster: barSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); - - test('Can render two bars within domain', () => { - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - ); - - const getBarGeometry = MockBarGeometry.fromBaseline( - { - x: 0, - y: 0, - width: 50, - height: 100, - color: 'red', - value: { - accessor: 'y1', - x: 0, - y: 10, - mark: null, - datum: [0, 10], - }, - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - }, - 'displayValue', - ); - expect(barGeometries[0]).toEqual(getBarGeometry()); - expect(barGeometries[1]).toEqual( - getBarGeometry({ - x: 50, - y: 50, - width: 50, - height: 50, - value: { - x: 1, - y: 5, - datum: [1, 5], - }, - }), - ); - expect(barGeometries.length).toBe(2); - }); - test('Can render bars with value labels', () => { - const valueFormatter = identity; - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - { valueFormatter, showValueLabel: true, isAlternatingValueLabel: true }, - ); - expect(barGeometries[0].displayValue).toBeDefined(); - }); - - test('Can hide value labels if no formatter or showValueLabels is false/undefined', () => { - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - {}, - ); - expect(barGeometries[0].displayValue).toBeUndefined(); - }); - - test('Can render bars with alternating value labels', () => { - const valueFormatter = identity; - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - { valueFormatter, showValueLabel: true, isAlternatingValueLabel: true }, - ); - expect(barGeometries[0].displayValue?.text).toBeDefined(); - expect(barGeometries[1].displayValue?.text).toBeUndefined(); }); + MockStore.addSpecs( + [spec, MockGlobalSpec.settingsNoMargins({ xDomain: [0, 1], theme: { colors: { vizColors: ['red'] } } })], + store, + ); + const { geometries } = computeSeriesGeometriesSelector(store.getState()); - test('Can render bars with contained value labels', () => { - const valueFormatter = identity; - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - { valueFormatter, showValueLabel: true, isValueContainedInElement: true }, - ); - expect(barGeometries[0].displayValue?.width).toBe(50); - }); - }); - describe('Multi series bar chart - ordinal', () => { - const spec1Id = 'bar1'; - const spec2Id = 'bar2'; - const barSeriesSpec1: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: spec1Id, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bar, - data: [ - [0, 10], - [1, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const barSeriesSpec2: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: spec2Id, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bar, - data: [ - [0, 20], - [1, 10], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const barSeriesMap = [barSeriesSpec1, barSeriesSpec2]; - const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: barSeriesDomains.xDomain, - totalBarsInCluster: barSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); const getBarGeometry = MockBarGeometry.fromBaseline( { x: 0, @@ -221,640 +64,824 @@ describe('Rendering bars', () => { mark: null, datum: [0, 10], }, - seriesIdentifier: { - specId: spec1Id, - key: 'spec{bar1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec), }, 'displayValue', ); - - test('can render first spec bars', () => { - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - ); - expect(barGeometries.length).toEqual(2); - expect(barGeometries[0]).toEqual( - getBarGeometry({ - x: 0, - y: 50, - width: 25, - height: 50, - value: { - x: 0, - y: 10, - datum: [0, 10], - }, - }), - ); - expect(barGeometries[1]).toEqual( - getBarGeometry({ - x: 50, - y: 75, - width: 25, - height: 25, - value: { - x: 1, - y: 5, - datum: [1, 5], - }, - }), - ); - }); - test('can render second spec bars', () => { - const { barGeometries } = renderBars( - 1, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], - xScale, - yScales.get(GROUP_ID)!, - 'blue', - LIGHT_THEME.barSeriesStyle, - ); - const getBarGeometry = MockBarGeometry.fromBaseline( - { - x: 0, - y: 0, - width: 50, - height: 100, - color: 'blue', - value: { - accessor: 'y1', - x: 0, - y: 10, - datum: [0, 10], - }, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{bar2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - }, - 'displayValue', - ); - expect(barGeometries.length).toEqual(2); - expect(barGeometries[0]).toEqual( - getBarGeometry({ - x: 25, - y: 0, - width: 25, - height: 100, - value: { - x: 0, - y: 20, - datum: [0, 20], - }, - }), - ); - expect(barGeometries[1]).toEqual( - getBarGeometry({ - x: 75, - y: 50, - width: 25, - height: 50, - value: { - x: 1, - y: 10, - datum: [1, 10], - }, - }), - ); - }); - }); - describe('Single series bar chart - linear', () => { - const barSeriesSpec: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bar, - data: [ - [0, 10], - [1, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Linear, - yScaleType: ScaleType.Linear, - }; - const barSeriesMap = [barSeriesSpec]; - const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: barSeriesDomains.xDomain, - totalBarsInCluster: barSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); - - test('Can render two bars', () => { - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - ); - const getBarGeometry = MockBarGeometry.fromBaseline( - { - x: 0, - y: 0, - width: 50, - height: 100, - color: 'red', - value: { - accessor: 'y1', - x: 0, - y: 10, - mark: null, - datum: [0, 10], - }, - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, + expect(geometries.bars[0].value[0]).toEqual(getBarGeometry()); + expect(geometries.bars[0].value[1]).toEqual( + getBarGeometry({ + x: 50, + y: 50, + width: 50, + height: 50, + value: { + x: 1, + y: 5, + datum: [1, 5], }, - 'displayValue', - ); - expect(barGeometries[0]).toEqual(getBarGeometry()); - expect(barGeometries[1]).toEqual( - getBarGeometry({ - x: 50, - y: 50, - width: 50, - height: 50, - value: { - x: 1, - y: 5, - datum: [1, 5], - }, - }), - ); - }); - }); - describe('Single series bar chart - log', () => { - const barSeriesSpec: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bar, - data: [ - [1, 0], - [2, 1], - [3, 2], - [4, 3], - [5, 4], - [6, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Linear, - yScaleType: ScaleType.Log, - }; - const barSeriesDomains = computeSeriesDomains([barSeriesSpec], new Map()); - const xScale = computeXScale({ - xDomain: barSeriesDomains.xDomain, - totalBarsInCluster: 1, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); - - test('Can render correct bar height', () => { - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - ); - expect(barGeometries.length).toBe(6); - expect(barGeometries[0].height).toBe(0); - expect(barGeometries[1].height).toBe(0); - expect(barGeometries[2].height).toBeGreaterThan(0); - expect(barGeometries[3].height).toBeGreaterThan(0); - }); + }), + ); + expect(geometries.bars[0].value.length).toBe(2); }); - describe('Multi series bar chart - linear', () => { - const spec1Id = 'bar1'; - const spec2Id = 'bar2'; - const barSeriesSpec1: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: spec1Id, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bar, - data: [ - [0, 10], - [1, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Linear, - yScaleType: ScaleType.Linear, - }; - const barSeriesSpec2: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: spec2Id, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bar, - data: [ - [0, 20], - [1, 10], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Linear, - yScaleType: ScaleType.Linear, - }; - const barSeriesMap = [barSeriesSpec1, barSeriesSpec2]; - const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: barSeriesDomains.xDomain, - totalBarsInCluster: barSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); - test('can render first spec bars', () => { - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - ); - const getBarGeometry = MockBarGeometry.fromBaseline( - { - x: 0, - y: 50, - width: 25, - height: 50, - color: 'red', - value: { - accessor: 'y1', - x: 0, - y: 10, - datum: [0, 10], - }, - seriesIdentifier: { - specId: spec1Id, - key: 'spec{bar1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - }, - 'displayValue', - ); - expect(barGeometries.length).toEqual(2); - expect(barGeometries[0]).toEqual(getBarGeometry()); - expect(barGeometries[1]).toEqual( - getBarGeometry({ - x: 50, - y: 75, - width: 25, - height: 25, - value: { - x: 1, - y: 5, - datum: [1, 5], - }, - }), - ); - }); - test('can render second spec bars', () => { - const { barGeometries } = renderBars( - 1, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], - xScale, - yScales.get(GROUP_ID)!, - 'blue', - LIGHT_THEME.barSeriesStyle, - ); - const getBarGeometry = MockBarGeometry.fromBaseline( - { - x: 25, - y: 0, - width: 25, - height: 100, - color: 'blue', - value: { - accessor: 'y1', - x: 0, - y: 20, - datum: [0, 20], - }, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{bar2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - }, - 'displayValue', - ); - expect(barGeometries.length).toEqual(2); - expect(barGeometries[0]).toEqual(getBarGeometry()); - expect(barGeometries[1]).toEqual( - getBarGeometry({ - x: 75, - y: 50, - width: 25, - height: 50, - color: 'blue', - value: { - x: 1, - y: 10, - datum: [1, 10], - }, - }), - ); - }); - }); - describe('Multi series bar chart - time', () => { - const spec1Id = 'bar1'; - const spec2Id = 'bar2'; - const barSeriesSpec1: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: spec1Id, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bar, - data: [ - [1546300800000, 10], - [1546387200000, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Time, - yScaleType: ScaleType.Linear, - }; - const barSeriesSpec2: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: spec2Id, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bar, - data: [ - [1546300800000, 20], - [1546387200000, 10], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Time, - yScaleType: ScaleType.Linear, - }; - const barSeriesMap = [barSeriesSpec1, barSeriesSpec2]; - const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: barSeriesDomains.xDomain, - totalBarsInCluster: barSeriesMap.length, - range: [0, 100], + describe('Single series bar chart - ordinal', () => { + test('Can render bars with value labels', () => { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + MockStore.addSpecs( + [ + MockSeriesSpec.bar({ + id: SPEC_ID, + groupId: GROUP_ID, + xScaleType: ScaleType.Ordinal, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], + data: [ + [-200, 0], + [0, 10], + [1, 5], + ], // first datum should be skipped as it's out of domain + displayValueSettings: { + showValueLabel: true, + isAlternatingValueLabel: true, + valueFormatter: identity, + }, + }), + MockGlobalSpec.settingsNoMargins({ xDomain: [0, 1], theme: { colors: { vizColors: ['red'] } } }), + ], + store, + ); + const { geometries } = computeSeriesGeometriesSelector(store.getState()); + expect(geometries.bars[0].value[0].displayValue).toBeDefined(); }); - const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); - test('can render first spec bars', () => { - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - ); - const getBarGeometry = MockBarGeometry.fromBaseline( - { - x: 0, - y: 50, - width: 25, - height: 50, - color: 'red', - value: { - accessor: 'y1', - x: 1546300800000, - y: 10, - datum: [1546300800000, 10], - }, - seriesIdentifier: { - specId: spec1Id, - key: 'spec{bar1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - }, - 'displayValue', - ); - expect(barGeometries.length).toEqual(2); - expect(barGeometries[0]).toEqual(getBarGeometry()); - expect(barGeometries[1]).toEqual( - getBarGeometry({ - x: 50, - y: 75, - width: 25, - height: 25, - value: { - accessor: 'y1', - x: 1546387200000, - y: 5, - mark: null, - datum: [1546387200000, 5], - }, - }), - ); + test('Can hide value labels if no formatter or showValueLabels is false/undefined', () => { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + MockStore.addSpecs( + [ + MockSeriesSpec.bar({ + id: SPEC_ID, + groupId: GROUP_ID, + xScaleType: ScaleType.Ordinal, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], + data: [ + [-200, 0], + [0, 10], + [1, 5], + ], // first datum should be skipped as it's out of domain + displayValueSettings: { + showValueLabel: false, + isAlternatingValueLabel: true, + valueFormatter: identity, + }, + }), + MockGlobalSpec.settingsNoMargins({ xDomain: [0, 1], theme: { colors: { vizColors: ['red'] } } }), + ], + store, + ); + const { geometries } = computeSeriesGeometriesSelector(store.getState()); + expect(geometries.bars[0].value[0].displayValue).toBeUndefined(); }); - test('can render second spec bars', () => { - const { barGeometries } = renderBars( - 1, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], - xScale, - yScales.get(GROUP_ID)!, - 'blue', - LIGHT_THEME.barSeriesStyle, - ); - const getBarGeometry = MockBarGeometry.fromBaseline( - { - x: 25, - y: 0, - width: 25, - height: 100, - color: 'blue', - value: { - accessor: 'y1', - x: 1546300800000, - y: 20, - mark: null, - datum: [1546300800000, 20], - }, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{bar2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - }, - 'displayValue', - ); - expect(barGeometries.length).toEqual(2); - expect(barGeometries[0]).toEqual(getBarGeometry()); - expect(barGeometries[1]).toEqual( - getBarGeometry({ - x: 75, - y: 50, - width: 25, - height: 50, - value: { - accessor: 'y1', - x: 1546387200000, - y: 10, - mark: null, - datum: [1546387200000, 10], - }, - }), - ); - }); - }); - describe('Remove points datum is not in domain', () => { - const barSeriesSpec: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bar, - data: [ - [0, 0], - [1, 1], - [2, 10], - [3, 3], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Linear, - yScaleType: ScaleType.Linear, - }; - const customYDomain = new Map(); - customYDomain.set(GROUP_ID, { - max: 1, - }); - const barSeriesDomains = computeSeriesDomains([barSeriesSpec], customYDomain, [], { - max: 2, - }); - const xScale = computeXScale({ - xDomain: barSeriesDomains.xDomain, - totalBarsInCluster: 1, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); + test('Can render bars with alternating value labels', () => { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + MockStore.addSpecs( + [ + MockSeriesSpec.bar({ + id: SPEC_ID, + groupId: GROUP_ID, + xScaleType: ScaleType.Ordinal, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], + data: [ + [-200, 0], + [0, 10], + [1, 5], + ], // first datum should be skipped as it's out of domain + displayValueSettings: { + showValueLabel: true, + isAlternatingValueLabel: true, + valueFormatter: identity, + }, + }), + MockGlobalSpec.settingsNoMargins({ xDomain: [0, 1], theme: { colors: { vizColors: ['red'] } } }), + ], + store, + ); + const { geometries } = computeSeriesGeometriesSelector(store.getState()); - test('Can render 3 bars', () => { - const { barGeometries, indexedGeometryMap } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - ); - expect(barGeometries.length).toBe(3); - // will be cut by the clipping areas in the rendering component - expect(barGeometries[2].height).toBe(1000); - expect(indexedGeometryMap.size).toBe(3); + expect(geometries.bars[0].value[0].displayValue?.text).toBeDefined(); + expect(geometries.bars[0].value[1].displayValue?.text).toBeUndefined(); }); - }); - describe('Renders minBarHeight', () => { - const minBarHeight = 8; - const data = [ - [1, -100000], - [2, -10000], - [3, -1000], - [4, -100], - [5, -10], - [6, -1], - [7, 0], - [8, -1], - [9, 0], - [10, 0], - [11, 1], - [12, 0], - [13, 1], - [14, 10], - [15, 100], - [16, 1000], - [17, 10000], - [18, 100000], - ]; - const barSeriesSpec: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bar, - data, - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Linear, - yScaleType: ScaleType.Linear, - minBarHeight, - }; - const customYDomain = new Map(); - const barSeriesDomains = computeSeriesDomains([barSeriesSpec], customYDomain); - const xScale = computeXScale({ - xDomain: barSeriesDomains.xDomain, - totalBarsInCluster: 1, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); - const expected = [-50, -8, -8, -8, -8, -8, 0, -8, 0, 0, 8, 0, 8, 8, 8, 8, 8, 50]; + test('Can render bars with contained value labels', () => { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + MockStore.addSpecs( + [ + MockSeriesSpec.bar({ + id: SPEC_ID, + groupId: GROUP_ID, + xScaleType: ScaleType.Ordinal, + yScaleType: ScaleType.Linear, + xAccessor: 0, + yAccessors: [1], + data: [ + [-200, 0], + [0, 10], + [1, 5], + ], // first datum should be skipped as it's out of domain + displayValueSettings: { + showValueLabel: true, + isValueContainedInElement: true, + valueFormatter: identity, + }, + }), + MockGlobalSpec.settingsNoMargins({ xDomain: [0, 1], theme: { colors: { vizColors: ['red'] } } }), + ], + store, + ); + const { geometries } = computeSeriesGeometriesSelector(store.getState()); - it('should render correct heights with positive minBarHeight', () => { - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - undefined, - undefined, - minBarHeight, - ); - const barHeights = barGeometries.map(({ height }) => height); - expect(barHeights).toEqual(expected); - }); - it('should render correct heights with negative minBarHeight', () => { - const { barGeometries } = renderBars( - 0, - barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - LIGHT_THEME.barSeriesStyle, - undefined, - undefined, - -minBarHeight, - ); - const barHeights = barGeometries.map(({ height }) => height); - expect(barHeights).toEqual(expected); + expect(geometries.bars[0].value[0].displayValue?.width).toBe(50); }); }); + // describe('Multi series bar chart - ordinal', () => { + // const spec1Id = 'bar1'; + // const spec2Id = 'bar2'; + // const barSeriesSpec1: BarSeriesSpec = { + // chartType: ChartTypes.XYAxis, + // specType: SpecTypes.Series, + // id: spec1Id, + // groupId: GROUP_ID, + // seriesType: SeriesTypes.Bar, + // data: [ + // [0, 10], + // [1, 5], + // ], + // xAccessor: 0, + // yAccessors: [1], + // xScaleType: ScaleType.Ordinal, + // yScaleType: ScaleType.Linear, + // }; + // const barSeriesSpec2: BarSeriesSpec = { + // chartType: ChartTypes.XYAxis, + // specType: SpecTypes.Series, + // id: spec2Id, + // groupId: GROUP_ID, + // seriesType: SeriesTypes.Bar, + // data: [ + // [0, 20], + // [1, 10], + // ], + // xAccessor: 0, + // yAccessors: [1], + // xScaleType: ScaleType.Ordinal, + // yScaleType: ScaleType.Linear, + // }; + // const barSeriesMap = [barSeriesSpec1, barSeriesSpec2]; + // const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); + // const xScale = computeXScale({ + // xDomain: barSeriesDomains.xDomain, + // totalBarsInCluster: barSeriesMap.length, + // range: [0, 100], + // }); + // const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); + // const getBarGeometry = MockBarGeometry.fromBaseline( + // { + // x: 0, + // y: 0, + // width: 50, + // height: 100, + // color: 'red', + // value: { + // accessor: 'y1', + // x: 0, + // y: 10, + // mark: null, + // }, + // seriesIdentifier: { + // specId: spec1Id, + // key: 'spec{bar1}yAccessor{1}splitAccessors{}', + // yAccessor: 1, + // splitAccessors: new Map(), + // seriesKeys: [1], + // }, + // }, + // 'displayValue', + // ); + // + // test('can render first spec bars', () => { + // const { barGeometries } = renderBars( + // 0, + // barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], + // xScale, + // yScales.get(GROUP_ID)!, + // 'red', + // LIGHT_THEME.barSeriesStyle, + // ); + // expect(barGeometries.length).toEqual(2); + // expect(barGeometries[0]).toEqual( + // getBarGeometry({ + // x: 0, + // y: 50, + // width: 25, + // height: 50, + // value: { + // x: 0, + // y: 10, + // }, + // }), + // ); + // expect(barGeometries[1]).toEqual( + // getBarGeometry({ + // x: 50, + // y: 75, + // width: 25, + // height: 25, + // value: { + // x: 1, + // y: 5, + // }, + // }), + // ); + // }); + // test('can render second spec bars', () => { + // const { barGeometries } = renderBars( + // 1, + // barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], + // xScale, + // yScales.get(GROUP_ID)!, + // 'blue', + // LIGHT_THEME.barSeriesStyle, + // ); + // const getBarGeometry = MockBarGeometry.fromBaseline( + // { + // x: 0, + // y: 0, + // width: 50, + // height: 100, + // color: 'blue', + // value: { + // accessor: 'y1', + // x: 0, + // y: 10, + // }, + // seriesIdentifier: { + // specId: spec2Id, + // key: 'spec{bar2}yAccessor{1}splitAccessors{}', + // yAccessor: 1, + // splitAccessors: new Map(), + // seriesKeys: [1], + // }, + // }, + // 'displayValue', + // ); + // expect(barGeometries.length).toEqual(2); + // expect(barGeometries[0]).toEqual( + // getBarGeometry({ + // x: 25, + // y: 0, + // width: 25, + // height: 100, + // value: { + // x: 0, + // y: 20, + // }, + // }), + // ); + // expect(barGeometries[1]).toEqual( + // getBarGeometry({ + // x: 75, + // y: 50, + // width: 25, + // height: 50, + // value: { + // x: 1, + // y: 10, + // }, + // }), + // ); + // }); + // }); + // describe('Single series bar chart - linear', () => { + // const barSeriesSpec: BarSeriesSpec = { + // chartType: ChartTypes.XYAxis, + // specType: SpecTypes.Series, + // id: SPEC_ID, + // groupId: GROUP_ID, + // seriesType: SeriesTypes.Bar, + // data: [ + // [0, 10], + // [1, 5], + // ], + // xAccessor: 0, + // yAccessors: [1], + // xScaleType: ScaleType.Linear, + // yScaleType: ScaleType.Linear, + // }; + // const barSeriesMap = [barSeriesSpec]; + // const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); + // const xScale = computeXScale({ + // xDomain: barSeriesDomains.xDomain, + // totalBarsInCluster: barSeriesMap.length, + // range: [0, 100], + // }); + // const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); + // + // test('Can render two bars', () => { + // const { barGeometries } = renderBars( + // 0, + // barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], + // xScale, + // yScales.get(GROUP_ID)!, + // 'red', + // LIGHT_THEME.barSeriesStyle, + // ); + // const getBarGeometry = MockBarGeometry.fromBaseline( + // { + // x: 0, + // y: 0, + // width: 50, + // height: 100, + // color: 'red', + // value: { + // accessor: 'y1', + // x: 0, + // y: 10, + // mark: null, + // }, + // seriesIdentifier: { + // specId: SPEC_ID, + // key: 'spec{spec_1}yAccessor{1}splitAccessors{}', + // yAccessor: 1, + // splitAccessors: new Map(), + // seriesKeys: [1], + // }, + // }, + // 'displayValue', + // ); + // expect(barGeometries[0]).toEqual(getBarGeometry()); + // expect(barGeometries[1]).toEqual( + // getBarGeometry({ + // x: 50, + // y: 50, + // width: 50, + // height: 50, + // value: { + // x: 1, + // y: 5, + // }, + // }), + // ); + // }); + // }); + // describe('Single series bar chart - log', () => { + // const barSeriesSpec: BarSeriesSpec = { + // chartType: ChartTypes.XYAxis, + // specType: SpecTypes.Series, + // id: SPEC_ID, + // groupId: GROUP_ID, + // seriesType: SeriesTypes.Bar, + // data: [ + // [1, 0], + // [2, 1], + // [3, 2], + // [4, 3], + // [5, 4], + // [6, 5], + // ], + // xAccessor: 0, + // yAccessors: [1], + // xScaleType: ScaleType.Linear, + // yScaleType: ScaleType.Log, + // }; + // const barSeriesDomains = computeSeriesDomains([barSeriesSpec], new Map()); + // const xScale = computeXScale({ + // xDomain: barSeriesDomains.xDomain, + // totalBarsInCluster: 1, + // range: [0, 100], + // }); + // const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); + // + // test('Can render correct bar height', () => { + // const { barGeometries } = renderBars( + // 0, + // barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], + // xScale, + // yScales.get(GROUP_ID)!, + // 'red', + // LIGHT_THEME.barSeriesStyle, + // ); + // expect(barGeometries.length).toBe(6); + // expect(barGeometries[0].height).toBe(0); + // expect(barGeometries[1].height).toBe(0); + // expect(barGeometries[2].height).toBeGreaterThan(0); + // expect(barGeometries[3].height).toBeGreaterThan(0); + // }); + // }); + // describe('Multi series bar chart - linear', () => { + // const spec1Id = 'bar1'; + // const spec2Id = 'bar2'; + // const barSeriesSpec1: BarSeriesSpec = { + // chartType: ChartTypes.XYAxis, + // specType: SpecTypes.Series, + // id: spec1Id, + // groupId: GROUP_ID, + // seriesType: SeriesTypes.Bar, + // data: [ + // [0, 10], + // [1, 5], + // ], + // xAccessor: 0, + // yAccessors: [1], + // xScaleType: ScaleType.Linear, + // yScaleType: ScaleType.Linear, + // }; + // const barSeriesSpec2: BarSeriesSpec = { + // chartType: ChartTypes.XYAxis, + // specType: SpecTypes.Series, + // id: spec2Id, + // groupId: GROUP_ID, + // seriesType: SeriesTypes.Bar, + // data: [ + // [0, 20], + // [1, 10], + // ], + // xAccessor: 0, + // yAccessors: [1], + // xScaleType: ScaleType.Linear, + // yScaleType: ScaleType.Linear, + // }; + // const barSeriesMap = [barSeriesSpec1, barSeriesSpec2]; + // const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); + // const xScale = computeXScale({ + // xDomain: barSeriesDomains.xDomain, + // totalBarsInCluster: barSeriesMap.length, + // range: [0, 100], + // }); + // const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); + // + // test('can render first spec bars', () => { + // const { barGeometries } = renderBars( + // 0, + // barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], + // xScale, + // yScales.get(GROUP_ID)!, + // 'red', + // LIGHT_THEME.barSeriesStyle, + // ); + // const getBarGeometry = MockBarGeometry.fromBaseline( + // { + // x: 0, + // y: 50, + // width: 25, + // height: 50, + // color: 'red', + // value: { + // accessor: 'y1', + // x: 0, + // y: 10, + // }, + // seriesIdentifier: { + // specId: spec1Id, + // key: 'spec{bar1}yAccessor{1}splitAccessors{}', + // yAccessor: 1, + // splitAccessors: new Map(), + // seriesKeys: [1], + // }, + // }, + // 'displayValue', + // ); + // expect(barGeometries.length).toEqual(2); + // expect(barGeometries[0]).toEqual(getBarGeometry()); + // expect(barGeometries[1]).toEqual( + // getBarGeometry({ + // x: 50, + // y: 75, + // width: 25, + // height: 25, + // value: { + // x: 1, + // y: 5, + // }, + // }), + // ); + // }); + // test('can render second spec bars', () => { + // const { barGeometries } = renderBars( + // 1, + // barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], + // xScale, + // yScales.get(GROUP_ID)!, + // 'blue', + // LIGHT_THEME.barSeriesStyle, + // ); + // const getBarGeometry = MockBarGeometry.fromBaseline( + // { + // x: 25, + // y: 0, + // width: 25, + // height: 100, + // color: 'blue', + // value: { + // accessor: 'y1', + // x: 0, + // y: 20, + // }, + // seriesIdentifier: { + // specId: spec2Id, + // key: 'spec{bar2}yAccessor{1}splitAccessors{}', + // yAccessor: 1, + // splitAccessors: new Map(), + // seriesKeys: [1], + // }, + // }, + // 'displayValue', + // ); + // expect(barGeometries.length).toEqual(2); + // expect(barGeometries[0]).toEqual(getBarGeometry()); + // expect(barGeometries[1]).toEqual( + // getBarGeometry({ + // x: 75, + // y: 50, + // width: 25, + // height: 50, + // color: 'blue', + // value: { + // x: 1, + // y: 10, + // }, + // }), + // ); + // }); + // }); + // describe('Multi series bar chart - time', () => { + // const spec1Id = 'bar1'; + // const spec2Id = 'bar2'; + // const barSeriesSpec1: BarSeriesSpec = { + // chartType: ChartTypes.XYAxis, + // specType: SpecTypes.Series, + // id: spec1Id, + // groupId: GROUP_ID, + // seriesType: SeriesTypes.Bar, + // data: [ + // [1546300800000, 10], + // [1546387200000, 5], + // ], + // xAccessor: 0, + // yAccessors: [1], + // xScaleType: ScaleType.Time, + // yScaleType: ScaleType.Linear, + // }; + // const barSeriesSpec2: BarSeriesSpec = { + // chartType: ChartTypes.XYAxis, + // specType: SpecTypes.Series, + // id: spec2Id, + // groupId: GROUP_ID, + // seriesType: SeriesTypes.Bar, + // data: [ + // [1546300800000, 20], + // [1546387200000, 10], + // ], + // xAccessor: 0, + // yAccessors: [1], + // xScaleType: ScaleType.Time, + // yScaleType: ScaleType.Linear, + // }; + // const barSeriesMap = [barSeriesSpec1, barSeriesSpec2]; + // const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); + // const xScale = computeXScale({ + // xDomain: barSeriesDomains.xDomain, + // totalBarsInCluster: barSeriesMap.length, + // range: [0, 100], + // }); + // const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); + // + // test('can render first spec bars', () => { + // const { barGeometries } = renderBars( + // 0, + // barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], + // xScale, + // yScales.get(GROUP_ID)!, + // 'red', + // LIGHT_THEME.barSeriesStyle, + // ); + // const getBarGeometry = MockBarGeometry.fromBaseline( + // { + // x: 0, + // y: 50, + // width: 25, + // height: 50, + // color: 'red', + // value: { + // accessor: 'y1', + // x: 1546300800000, + // y: 10, + // }, + // seriesIdentifier: { + // specId: spec1Id, + // key: 'spec{bar1}yAccessor{1}splitAccessors{}', + // yAccessor: 1, + // splitAccessors: new Map(), + // seriesKeys: [1], + // }, + // }, + // 'displayValue', + // ); + // expect(barGeometries.length).toEqual(2); + // expect(barGeometries[0]).toEqual(getBarGeometry()); + // expect(barGeometries[1]).toEqual( + // getBarGeometry({ + // x: 50, + // y: 75, + // width: 25, + // height: 25, + // value: { + // accessor: 'y1', + // x: 1546387200000, + // y: 5, + // mark: null, + // }, + // }), + // ); + // }); + // test('can render second spec bars', () => { + // const { barGeometries } = renderBars( + // 1, + // barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], + // xScale, + // yScales.get(GROUP_ID)!, + // 'blue', + // LIGHT_THEME.barSeriesStyle, + // ); + // const getBarGeometry = MockBarGeometry.fromBaseline( + // { + // x: 25, + // y: 0, + // width: 25, + // height: 100, + // color: 'blue', + // value: { + // accessor: 'y1', + // x: 1546300800000, + // y: 20, + // mark: null, + // }, + // seriesIdentifier: { + // specId: spec2Id, + // key: 'spec{bar2}yAccessor{1}splitAccessors{}', + // yAccessor: 1, + // splitAccessors: new Map(), + // seriesKeys: [1], + // }, + // }, + // 'displayValue', + // ); + // + // expect(barGeometries.length).toEqual(2); + // expect(barGeometries[0]).toEqual(getBarGeometry()); + // expect(barGeometries[1]).toEqual( + // getBarGeometry({ + // x: 75, + // y: 50, + // width: 25, + // height: 50, + // value: { + // accessor: 'y1', + // x: 1546387200000, + // y: 10, + // mark: null, + // }, + // }), + // ); + // }); + // }); + // describe('Remove points datum is not in domain', () => { + // const barSeriesSpec: BarSeriesSpec = { + // chartType: ChartTypes.XYAxis, + // specType: SpecTypes.Series, + // id: SPEC_ID, + // groupId: GROUP_ID, + // seriesType: SeriesTypes.Bar, + // data: [ + // [0, 0], + // [1, 1], + // [2, 10], + // [3, 3], + // ], + // xAccessor: 0, + // yAccessors: [1], + // xScaleType: ScaleType.Linear, + // yScaleType: ScaleType.Linear, + // }; + // const customYDomain = new Map(); + // customYDomain.set(GROUP_ID, { + // max: 1, + // }); + // const barSeriesDomains = computeSeriesDomains([barSeriesSpec], customYDomain, [], { + // max: 2, + // }); + // const xScale = computeXScale({ + // xDomain: barSeriesDomains.xDomain, + // totalBarsInCluster: 1, + // range: [0, 100], + // }); + // const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); + // + // test('Can render 3 bars', () => { + // const { barGeometries, indexedGeometryMap } = renderBars( + // 0, + // barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], + // xScale, + // yScales.get(GROUP_ID)!, + // 'red', + // LIGHT_THEME.barSeriesStyle, + // ); + // expect(barGeometries.length).toBe(3); + // // will be cut by the clipping areas in the rendering component + // expect(barGeometries[2].height).toBe(1000); + // expect(indexedGeometryMap.size).toBe(3); + // }); + // }); + // describe('Renders minBarHeight', () => { + // const minBarHeight = 8; + // const data = [ + // [1, -100000], + // [2, -10000], + // [3, -1000], + // [4, -100], + // [5, -10], + // [6, -1], + // [7, 0], + // [8, -1], + // [9, 0], + // [10, 0], + // [11, 1], + // [12, 0], + // [13, 1], + // [14, 10], + // [15, 100], + // [16, 1000], + // [17, 10000], + // [18, 100000], + // ]; + // const barSeriesSpec: BarSeriesSpec = { + // chartType: ChartTypes.XYAxis, + // specType: SpecTypes.Series, + // id: SPEC_ID, + // groupId: GROUP_ID, + // seriesType: SeriesTypes.Bar, + // data, + // xAccessor: 0, + // yAccessors: [1], + // xScaleType: ScaleType.Linear, + // yScaleType: ScaleType.Linear, + // minBarHeight, + // }; + // + // const customYDomain = new Map(); + // const barSeriesDomains = computeSeriesDomains([barSeriesSpec], customYDomain); + // const xScale = computeXScale({ + // xDomain: barSeriesDomains.xDomain, + // totalBarsInCluster: 1, + // range: [0, 100], + // }); + // const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); + // const expected = [-50, -8, -8, -8, -8, -8, 0, -8, 0, 0, 8, 0, 8, 8, 8, 8, 8, 50]; + // + // it('should render correct heights with positive minBarHeight', () => { + // const { barGeometries } = renderBars( + // 0, + // barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], + // xScale, + // yScales.get(GROUP_ID)!, + // 'red', + // LIGHT_THEME.barSeriesStyle, + // undefined, + // undefined, + // minBarHeight, + // ); + // const barHeights = barGeometries.map(({ height }) => height); + // expect(barHeights).toEqual(expected); + // }); + // it('should render correct heights with negative minBarHeight', () => { + // const { barGeometries } = renderBars( + // 0, + // barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], + // xScale, + // yScales.get(GROUP_ID)!, + // 'red', + // LIGHT_THEME.barSeriesStyle, + // undefined, + // undefined, + // -minBarHeight, + // ); + // const barHeights = barGeometries.map(({ height }) => height); + // expect(barHeights).toEqual(expected); + // }); + // }); }); diff --git a/src/chart_types/xy_chart/rendering/rendering.bubble.test.ts b/src/chart_types/xy_chart/rendering/rendering.bubble.test.ts index 57727865c9..6f0116443e 100644 --- a/src/chart_types/xy_chart/rendering/rendering.bubble.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.bubble.test.ts @@ -17,81 +17,23 @@ * under the License. */ -import { ChartTypes } from '../..'; +import { MockPointGeometry } from '../../../mocks'; +import { MockSeriesIdentifier } from '../../../mocks/series/series_identifiers'; +import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs'; +import { MockStore } from '../../../mocks/store'; import { ScaleType } from '../../../scales/constants'; -import { SpecTypes } from '../../../specs/constants'; -import { BubbleGeometry, PointGeometry } from '../../../utils/geometry'; -import { GroupId } from '../../../utils/ids'; -import { LIGHT_THEME } from '../../../utils/themes/light_theme'; -import { computeSeriesDomains } from '../state/utils/utils'; -import { IndexedGeometryMap } from '../utils/indexed_geometry_map'; -import { computeXScale, computeYScales } from '../utils/scales'; -import { BubbleSeriesSpec, DomainRange, SeriesTypes } from '../utils/specs'; -import { renderBubble } from './rendering'; +import { Position } from '../../../utils/commons'; +import { PointGeometry } from '../../../utils/geometry'; +import { computeSeriesGeometriesSelector } from '../state/selectors/compute_series_geometries'; const SPEC_ID = 'spec_1'; const GROUP_ID = 'group_1'; describe('Rendering points - bubble', () => { - describe('Empty bubble for missing data', () => { - const pointSeriesSpec: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, - data: [ - [0, 10], - [1, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - renderedBubble = renderBubble( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - { ...pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], data: [] }, - xScale, - yScales.get(GROUP_ID)!, - 'red', - false, - LIGHT_THEME.bubbleSeriesStyle, - { - enabled: false, - }, - false, - ); - }); - test('Can render the geometry without a bubble', () => { - const { bubbleGeometry } = renderedBubble; - expect(bubbleGeometry.points).toHaveLength(0); - expect(bubbleGeometry.color).toBe('red'); - expect(bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(bubbleGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); - }); - }); describe('Single series bubble chart - ordinal', () => { - const pointSeriesSpec: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const spec = MockSeriesSpec.bubble({ id: SPEC_ID, groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, data: [ [0, 10], [1, 5], @@ -100,109 +42,78 @@ describe('Rendering points - bubble', () => { yAccessors: [1], xScaleType: ScaleType.Ordinal, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - renderedBubble = renderBubble( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - false, - LIGHT_THEME.bubbleSeriesStyle, - { - enabled: false, - }, - false, - ); }); + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([spec, settings], store); + const { + geometries: { bubbles }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); test('Can render a bubble', () => { - const { bubbleGeometry } = renderedBubble; + const [{ value: bubbleGeometry }] = bubbles; expect(bubbleGeometry.points).toHaveLength(2); expect(bubbleGeometry.color).toBe('red'); expect(bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); expect(bubbleGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); }); test('Can render two points', () => { - const { - bubbleGeometry: { points }, - indexedGeometryMap, - } = renderedBubble; - - expect(points[0]).toEqual(({ - x: 0, - y: 0, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], + const [ + { + value: { points }, }, - styleOverrides: undefined, - value: { - accessor: 'y1', + ] = bubbles; + + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 10, - mark: null, - datum: [0, 10], - }, - transform: { - x: 25, y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 50, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 5, - mark: null, - datum: [1, 5], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec), + styleOverrides: undefined, + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + datum: [0, 10], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 50, + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(spec), + value: { + accessor: 'y1', + x: 1, + y: 5, + mark: null, + datum: [1, 5], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); + expect(geometriesIndex.size).toEqual(points.length); }); }); describe('Multi series bubble chart - ordinal', () => { const spec1Id = 'point1'; const spec2Id = 'point2'; - const pointSeriesSpec1: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec1 = MockSeriesSpec.bubble({ id: spec1Id, groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, data: [ [0, 10], [1, 5], @@ -211,13 +122,10 @@ describe('Rendering points - bubble', () => { yAccessors: [1], xScaleType: ScaleType.Ordinal, yScaleType: ScaleType.Linear, - }; - const pointSeriesSpec2: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const pointSeriesSpec2 = MockSeriesSpec.bubble({ id: spec2Id, groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, data: [ [0, 20], [1, 10], @@ -226,185 +134,130 @@ describe('Rendering points - bubble', () => { yAccessors: [1], xScaleType: ScaleType.Ordinal, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec1, pointSeriesSpec2]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - - let firstBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - let secondBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - firstBubble = renderBubble( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - false, - LIGHT_THEME.bubbleSeriesStyle, - { - enabled: false, - }, - false, - ); - secondBubble = renderBubble( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], - xScale, - yScales.get(GROUP_ID)!, - 'blue', - false, - LIGHT_THEME.bubbleSeriesStyle, - { - enabled: false, - }, - false, - ); }); + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec1, pointSeriesSpec2, settings], store); + const { + geometries: { bubbles }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); test('Can render two ordinal bubbles', () => { - expect(firstBubble.bubbleGeometry.points).toHaveLength(2); - expect(firstBubble.bubbleGeometry.color).toBe('red'); - expect(firstBubble.bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(firstBubble.bubbleGeometry.seriesIdentifier.specId).toEqual(spec1Id); + const [{ value: firstBubble }, { value: secondBubble }] = bubbles; + expect(firstBubble.points).toHaveLength(2); + expect(firstBubble.color).toBe('red'); + expect(firstBubble.seriesIdentifier.seriesKeys).toEqual([1]); + expect(firstBubble.seriesIdentifier.specId).toEqual(spec1Id); - expect(secondBubble.bubbleGeometry.points).toHaveLength(2); - expect(secondBubble.bubbleGeometry.color).toBe('blue'); - expect(secondBubble.bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(secondBubble.bubbleGeometry.seriesIdentifier.specId).toEqual(spec2Id); + expect(secondBubble.points).toHaveLength(2); + expect(secondBubble.color).toBe('blue'); + expect(secondBubble.seriesIdentifier.seriesKeys).toEqual([1]); + expect(secondBubble.seriesIdentifier.specId).toEqual(spec2Id); + expect(geometriesIndex.size).toEqual(4); }); test('can render first spec points', () => { - const { - bubbleGeometry: { points }, - indexedGeometryMap, - } = firstBubble; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - key: 'spec{point1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], + const [ + { + value: { points }, }, - value: { - accessor: 'y1', + ] = bubbles; + expect(points.length).toEqual(2); + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 10, - mark: null, - datum: [0, 10], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 50, - y: 75, - color: 'red', - radius: 0, - seriesIdentifier: { - specId: spec1Id, - key: 'spec{point1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 5, - mark: null, - datum: [1, 5], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec1), + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + datum: [0, 10], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 50, + y: 75, + color: 'red', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec1), + value: { + accessor: 'y1', + x: 1, + y: 5, + mark: null, + datum: [1, 5], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); }); test('can render second spec points', () => { - const { - bubbleGeometry: { points }, - indexedGeometryMap, - } = secondBubble; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 0, - color: 'blue', - radius: 0, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{point2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], + const [ + , + { + value: { points }, }, - value: { - accessor: 'y1', + ] = bubbles; + expect(points.length).toEqual(2); + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 20, - mark: null, - datum: [0, 20], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 50, - y: 50, - color: 'blue', - radius: 0, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{point2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 10, - mark: null, - datum: [1, 10], - }, - transform: { - x: 25, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + color: 'blue', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec2), + value: { + accessor: 'y1', + x: 0, + y: 20, + mark: null, + datum: [0, 20], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 50, + y: 50, + color: 'blue', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec2), + value: { + accessor: 'y1', + x: 1, + y: 10, + mark: null, + datum: [1, 10], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); }); }); describe('Single series bubble chart - linear', () => { - const pointSeriesSpec: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec = MockSeriesSpec.bubble({ id: SPEC_ID, groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, data: [ [0, 10], [1, 5], @@ -413,107 +266,77 @@ describe('Rendering points - bubble', () => { yAccessors: [1], xScaleType: ScaleType.Linear, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - - let renderedBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec, settings], store); + const { + geometries: { bubbles }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); - beforeEach(() => { - renderedBubble = renderBubble( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - false, - LIGHT_THEME.bubbleSeriesStyle, - { - enabled: false, - }, - false, - ); - }); test('Can render a linear bubble', () => { - expect(renderedBubble.bubbleGeometry.points).toHaveLength(2); - expect(renderedBubble.bubbleGeometry.color).toBe('red'); - expect(renderedBubble.bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(renderedBubble.bubbleGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); + const [{ value: bubbleGeometry }] = bubbles; + expect(bubbleGeometry.points).toHaveLength(2); + expect(bubbleGeometry.color).toBe('red'); + expect(bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); + expect(bubbleGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); }); test('Can render two points', () => { - const { - bubbleGeometry: { points }, - indexedGeometryMap, - } = renderedBubble; - expect(points[0]).toEqual(({ - x: 0, - y: 0, - color: 'red', - radius: 0, - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 0, - y: 10, - mark: null, - datum: [0, 10], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 50, - color: 'red', - radius: 0, - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 5, - mark: null, - datum: [1, 5], + const [ + { + value: { points }, }, - transform: { + ] = bubbles; + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + color: 'red', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + datum: [0, 10], + }, + transform: { + x: 0, + y: 0, + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 50, + color: 'red', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 1, + y: 5, + mark: null, + datum: [1, 5], + }, + transform: { + x: 0, + y: 0, + }, + }), + ); + expect(geometriesIndex.size).toEqual(points.length); }); }); describe('Multi series bubble chart - linear', () => { const spec1Id = 'point1'; const spec2Id = 'point2'; - const pointSeriesSpec1: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec1 = MockSeriesSpec.bubble({ id: spec1Id, groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, data: [ [0, 10], [1, 5], @@ -522,13 +345,10 @@ describe('Rendering points - bubble', () => { yAccessors: [1], xScaleType: ScaleType.Linear, yScaleType: ScaleType.Linear, - }; - const pointSeriesSpec2: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const pointSeriesSpec2 = MockSeriesSpec.bubble({ id: spec2Id, groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, data: [ [0, 20], [1, 10], @@ -537,184 +357,131 @@ describe('Rendering points - bubble', () => { yAccessors: [1], xScaleType: ScaleType.Linear, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec1, pointSeriesSpec2]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let firstBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - let secondBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec1, pointSeriesSpec2, settings], store); + const { + geometries: { bubbles }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); - beforeEach(() => { - firstBubble = renderBubble( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - false, - LIGHT_THEME.bubbleSeriesStyle, - { - enabled: false, - }, - false, - ); - secondBubble = renderBubble( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], - xScale, - yScales.get(GROUP_ID)!, - 'blue', - false, - LIGHT_THEME.bubbleSeriesStyle, - { - enabled: false, - }, - false, - ); - }); test('can render two linear bubbles', () => { - expect(firstBubble.bubbleGeometry.points).toHaveLength(2); - expect(firstBubble.bubbleGeometry.color).toBe('red'); - expect(firstBubble.bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(firstBubble.bubbleGeometry.seriesIdentifier.specId).toEqual(spec1Id); + const [{ value: firstBubble }, { value: secondBubble }] = bubbles; + expect(firstBubble.points).toHaveLength(2); + expect(firstBubble.color).toBe('red'); + expect(firstBubble.seriesIdentifier.seriesKeys).toEqual([1]); + expect(firstBubble.seriesIdentifier.specId).toEqual(spec1Id); - expect(secondBubble.bubbleGeometry.points).toHaveLength(2); - expect(secondBubble.bubbleGeometry.color).toBe('blue'); - expect(secondBubble.bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(secondBubble.bubbleGeometry.seriesIdentifier.specId).toEqual(spec2Id); + expect(secondBubble.points).toHaveLength(2); + expect(secondBubble.color).toBe('blue'); + expect(secondBubble.seriesIdentifier.seriesKeys).toEqual([1]); + expect(secondBubble.seriesIdentifier.specId).toEqual(spec2Id); + expect(geometriesIndex.size).toEqual(4); }); test('can render first spec points', () => { - const { - bubbleGeometry: { points }, - indexedGeometryMap, - } = firstBubble; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - key: 'spec{point1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 0, - y: 10, - mark: null, - datum: [0, 10], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 75, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - key: 'spec{point1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 5, - mark: null, - datum: [1, 5], + const [ + { + value: { points }, }, - transform: { + ] = bubbles; + expect(points.length).toEqual(2); + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec1), + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + datum: [0, 10], + }, + transform: { + x: 0, + y: 0, + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 75, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec1), + value: { + accessor: 'y1', + x: 1, + y: 5, + mark: null, + datum: [1, 5], + }, + transform: { + x: 0, + y: 0, + }, + }), + ); }); test('can render second spec points', () => { - const { - bubbleGeometry: { points }, - indexedGeometryMap, - } = secondBubble; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 0, - color: 'blue', - radius: 0, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{point2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 0, - y: 20, - mark: null, - datum: [0, 20], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 50, - color: 'blue', - radius: 0, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{point2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 10, - mark: null, - datum: [1, 10], + const [ + , + { + value: { points }, }, - transform: { + ] = bubbles; + expect(points.length).toEqual(2); + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + color: 'blue', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec2), + value: { + accessor: 'y1', + x: 0, + y: 20, + mark: null, + datum: [0, 20], + }, + transform: { + x: 0, + y: 0, + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 50, + color: 'blue', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec2), + value: { + accessor: 'y1', + x: 1, + y: 10, + mark: null, + datum: [1, 10], + }, + transform: { + x: 0, + y: 0, + }, + }), + ); }); }); describe('Single series bubble chart - time', () => { - const pointSeriesSpec: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec = MockSeriesSpec.bubble({ id: SPEC_ID, groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, data: [ [1546300800000, 10], [1546387200000, 5], @@ -723,107 +490,77 @@ describe('Rendering points - bubble', () => { yAccessors: [1], xScaleType: ScaleType.Time, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - - let renderedBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec, settings], store); + const { + geometries: { bubbles }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); - beforeEach(() => { - renderedBubble = renderBubble( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - false, - LIGHT_THEME.bubbleSeriesStyle, - { - enabled: false, - }, - false, - ); - }); test('Can render a time bubble', () => { - expect(renderedBubble.bubbleGeometry.points).toHaveLength(2); - expect(renderedBubble.bubbleGeometry.color).toBe('red'); - expect(renderedBubble.bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(renderedBubble.bubbleGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); + const [{ value: renderedBubble }] = bubbles; + expect(renderedBubble.points).toHaveLength(2); + expect(renderedBubble.color).toBe('red'); + expect(renderedBubble.seriesIdentifier.seriesKeys).toEqual([1]); + expect(renderedBubble.seriesIdentifier.specId).toEqual(SPEC_ID); }); test('Can render two points', () => { - const { - bubbleGeometry: { points }, - indexedGeometryMap, - } = renderedBubble; - expect(points[0]).toEqual(({ - x: 0, - y: 0, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1546300800000, - y: 10, - mark: null, - datum: [1546300800000, 10], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1546387200000, - y: 5, - mark: null, - datum: [1546387200000, 5], + const [ + { + value: { points }, }, - transform: { + ] = bubbles; + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 1546300800000, + y: 10, + mark: null, + datum: [1546300800000, 10], + }, + transform: { + x: 0, + y: 0, + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 1546387200000, + y: 5, + mark: null, + datum: [1546387200000, 5], + }, + transform: { + x: 0, + y: 0, + }, + }), + ); + expect(geometriesIndex.size).toEqual(points.length); }); }); describe('Multi series bubble chart - time', () => { const spec1Id = 'point1'; const spec2Id = 'point2'; - const pointSeriesSpec1: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec1 = MockSeriesSpec.bubble({ id: spec1Id, groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, data: [ [1546300800000, 10], [1546387200000, 5], @@ -832,13 +569,10 @@ describe('Rendering points - bubble', () => { yAccessors: [1], xScaleType: ScaleType.Time, yScaleType: ScaleType.Linear, - }; - const pointSeriesSpec2: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const pointSeriesSpec2 = MockSeriesSpec.bubble({ id: spec2Id, groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, data: [ [1546300800000, 20], [1546387200000, 10], @@ -847,173 +581,117 @@ describe('Rendering points - bubble', () => { yAccessors: [1], xScaleType: ScaleType.Time, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec1, pointSeriesSpec2]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - - let firstBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - let secondBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - firstBubble = renderBubble( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - false, - LIGHT_THEME.bubbleSeriesStyle, - { - enabled: false, - }, - false, - ); - secondBubble = renderBubble( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], - xScale, - yScales.get(GROUP_ID)!, - 'blue', - false, - LIGHT_THEME.bubbleSeriesStyle, + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec1, pointSeriesSpec2, settings], store); + const { + geometries: { bubbles }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); + test('can render first spec points', () => { + const [ { - enabled: false, + value: { points }, }, - false, - ); - }); - test('can render first spec points', () => { - const { - bubbleGeometry: { points }, - indexedGeometryMap, - } = firstBubble; + ] = bubbles; expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - key: 'spec{point1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1546300800000, - y: 10, - mark: null, - datum: [1546300800000, 10], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 75, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - key: 'spec{point1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1546387200000, - y: 5, - mark: null, - datum: [1546387200000, 5], - }, - transform: { + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec1), + value: { + accessor: 'y1', + x: 1546300800000, + y: 10, + mark: null, + datum: [1546300800000, 10], + }, + transform: { + x: 0, + y: 0, + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 75, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec1), + value: { + accessor: 'y1', + x: 1546387200000, + y: 5, + mark: null, + datum: [1546387200000, 5], + }, + transform: { + x: 0, + y: 0, + }, + }), + ); + expect(geometriesIndex.size).toEqual(4); }); test('can render second spec points', () => { - const { - bubbleGeometry: { points }, - indexedGeometryMap, - } = secondBubble; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 0, - radius: 0, - color: 'blue', - seriesIdentifier: { - specId: spec2Id, - key: 'spec{point2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1546300800000, - y: 20, - mark: null, - datum: [1546300800000, 20], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 50, - radius: 0, - color: 'blue', - seriesIdentifier: { - specId: spec2Id, - key: 'spec{point2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1546387200000, - y: 10, - mark: null, - datum: [1546387200000, 10], + const [ + , + { + value: { points }, }, - transform: { + ] = bubbles; + expect(points.length).toEqual(2); + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + radius: 0, + color: 'blue', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec2), + value: { + accessor: 'y1', + x: 1546300800000, + y: 20, + mark: null, + datum: [1546300800000, 20], + }, + transform: { + x: 0, + y: 0, + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 50, + radius: 0, + color: 'blue', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec2), + value: { + accessor: 'y1', + x: 1546387200000, + y: 10, + mark: null, + datum: [1546387200000, 10], + }, + transform: { + x: 0, + y: 0, + }, + }), + ); }); }); describe('Single series bubble chart - y log', () => { - const pointSeriesSpec: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec = MockSeriesSpec.bubble({ id: SPEC_ID, groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, data: [ [0, 10], [1, 5], @@ -1029,53 +707,34 @@ describe('Rendering points - bubble', () => { yAccessors: [1], xScaleType: ScaleType.Linear, yScaleType: ScaleType.Log, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 90], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec, settings], store); + const { + geometries: { bubbles }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); - let renderedBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - renderedBubble = renderBubble( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - false, - LIGHT_THEME.bubbleSeriesStyle, - { - enabled: false, - }, - false, - ); - }); test('Can render a splitted bubble', () => { - expect(renderedBubble.bubbleGeometry.points).toHaveLength(7); - expect(renderedBubble.bubbleGeometry.color).toBe('red'); - expect(renderedBubble.bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(renderedBubble.bubbleGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); + const [{ value: renderedBubble }] = bubbles; + expect(renderedBubble.points).toHaveLength(7); + expect(renderedBubble.color).toBe('red'); + expect(renderedBubble.seriesIdentifier.seriesKeys).toEqual([1]); + expect(renderedBubble.seriesIdentifier.specId).toEqual(SPEC_ID); }); test('Can render points', () => { - const { - bubbleGeometry: { points }, - indexedGeometryMap, - } = renderedBubble; + const [ + { + value: { points }, + }, + ] = bubbles; // all the points minus the undefined ones on a log scale expect(points.length).toBe(7); // all the points expect null geometries - expect(indexedGeometryMap.size).toEqual(8); + expect(geometriesIndex.size).toEqual(8); - const zeroValueIndexdGeometry = indexedGeometryMap.find(null, { + const zeroValueIndexdGeometry = geometriesIndex.find(null, { x: 56.25, y: 100, }); @@ -1089,12 +748,8 @@ describe('Rendering points - bubble', () => { }); }); describe('Remove points datum is not in domain', () => { - const pointSeriesSpec: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec = MockSeriesSpec.bubble({ id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, data: [ [0, 0], [1, 1], @@ -1105,175 +760,60 @@ describe('Rendering points - bubble', () => { yAccessors: [1], xScaleType: ScaleType.Linear, yScaleType: ScaleType.Linear, - }; - const customYDomain = new Map(); - customYDomain.set(GROUP_ID, { - max: 1, - }); - const pointSeriesDomains = computeSeriesDomains([pointSeriesSpec], customYDomain, [], { - max: 2, - }); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: 1, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - renderedBubble = renderBubble( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - false, - LIGHT_THEME.bubbleSeriesStyle, + const settings = MockGlobalSpec.settingsNoMargins({ + xDomain: { max: 2 }, + theme: { colors: { vizColors: ['red', 'blue'] } }, + }); + const axis = MockGlobalSpec.axis({ position: Position.Left, hide: true, domain: { max: 1 } }); + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + MockStore.addSpecs([pointSeriesSpec, axis, settings], store); + const { + geometries: { bubbles }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); + test('Can render two points', () => { + const [ { - enabled: false, + value: { points }, }, - false, - ); - }); - test('Can render two points', () => { - const { - bubbleGeometry: { points }, - indexedGeometryMap, - } = renderedBubble; + ] = bubbles; // will not render the 3rd point that is out of y domain expect(points.length).toBe(2); // will keep the 3rd point as an indexedGeometry - expect(indexedGeometryMap.size).toEqual(3); - expect(points[0]).toEqual(({ - x: 0, - y: 100, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', + expect(geometriesIndex.size).toEqual(3); + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, + y: 100, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 0, + y: 0, + mark: null, + datum: [0, 0], + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 50, y: 0, - mark: null, - datum: [0, 0], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 50, - y: 0, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 1, - mark: null, - datum: [1, 1], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - }); - }); - - describe('Error guards for scaled values', () => { - const pointSeriesSpec: BubbleSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Bubble, - data: [ - [0, 10], - [1, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedBubble: { - bubbleGeometry: BubbleGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - renderedBubble = renderBubble( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - false, - LIGHT_THEME.bubbleSeriesStyle, - { - enabled: false, - }, - false, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 1, + y: 1, + mark: null, + datum: [1, 1], + }, + }), ); }); - - describe('xScale values throw error', () => { - beforeAll(() => { - jest.spyOn(xScale, 'scaleOrThrow').mockImplementation(() => { - throw new Error(); - }); - }); - - it('Should have empty bubble', () => { - const { bubbleGeometry } = renderedBubble; - expect(bubbleGeometry.points).toHaveLength(2); - expect(bubbleGeometry.color).toBe('red'); - expect(bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(bubbleGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); - }); - }); - - describe('yScale values throw error', () => { - beforeAll(() => { - jest.spyOn(yScales.get(GROUP_ID)!, 'scaleOrThrow').mockImplementation(() => { - throw new Error(); - }); - }); - - it('Should have empty bubble', () => { - const { bubbleGeometry } = renderedBubble; - expect(bubbleGeometry.points).toHaveLength(2); - expect(bubbleGeometry.color).toBe('red'); - expect(bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(bubbleGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); - }); - }); }); }); diff --git a/src/chart_types/xy_chart/rendering/rendering.lines.test.ts b/src/chart_types/xy_chart/rendering/rendering.lines.test.ts index 1fd525240f..c6772af436 100644 --- a/src/chart_types/xy_chart/rendering/rendering.lines.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.lines.test.ts @@ -17,84 +17,24 @@ * under the License. */ -import { ChartTypes } from '../..'; +import { MockPointGeometry } from '../../../mocks'; +import { MockSeriesIdentifier } from '../../../mocks/series/series_identifiers'; +import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs'; +import { MockStore } from '../../../mocks/store'; import { ScaleType } from '../../../scales/constants'; -import { SpecTypes } from '../../../specs/constants'; -import { CurveType } from '../../../utils/curves'; -import { LineGeometry, PointGeometry } from '../../../utils/geometry'; -import { GroupId } from '../../../utils/ids'; -import { LIGHT_THEME } from '../../../utils/themes/light_theme'; -import { computeSeriesDomains } from '../state/utils/utils'; -import { IndexedGeometryMap } from '../utils/indexed_geometry_map'; -import { computeXScale, computeYScales } from '../utils/scales'; -import { LineSeriesSpec, DomainRange, SeriesTypes } from '../utils/specs'; -import { renderLine } from './rendering'; +import { Position } from '../../../utils/commons'; +import { PointGeometry } from '../../../utils/geometry'; +import { computeSeriesGeometriesSelector } from '../state/selectors/compute_series_geometries'; +import { SeriesTypes } from '../utils/specs'; const SPEC_ID = 'spec_1'; const GROUP_ID = 'group_1'; describe('Rendering points - line', () => { - describe('Empty line for missing data', () => { - const pointSeriesSpec: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Line, - data: [ - [0, 10], - [1, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - renderedLine = renderLine( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - { ...pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], data: [] }, - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, - { - enabled: false, - }, - ); - }); - test('Can render the geometry without a line', () => { - const { lineGeometry } = renderedLine; - expect(lineGeometry.line).toBe(''); - expect(lineGeometry.color).toBe('red'); - expect(lineGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(lineGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); - expect(lineGeometry.transform).toEqual({ x: 25, y: 0 }); - }); - }); describe('Single series line chart - ordinal', () => { - const pointSeriesSpec: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec = MockSeriesSpec.line({ id: SPEC_ID, groupId: GROUP_ID, - seriesType: SeriesTypes.Line, data: [ [0, 10], [1, 5], @@ -103,38 +43,17 @@ describe('Rendering points - line', () => { yAccessors: [1], xScaleType: ScaleType.Ordinal, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red'] } } }); + MockStore.addSpecs([pointSeriesSpec, settings], store); + const { + geometries: { lines }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); - beforeEach(() => { - renderedLine = renderLine( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, - { - enabled: false, - }, - ); - }); test('Can render a line', () => { - const { lineGeometry } = renderedLine; + const [{ value: lineGeometry }] = lines; expect(lineGeometry.line).toBe('M0,0L50,50'); expect(lineGeometry.color).toBe('red'); expect(lineGeometry.seriesIdentifier.seriesKeys).toEqual([1]); @@ -142,69 +61,60 @@ describe('Rendering points - line', () => { expect(lineGeometry.transform).toEqual({ x: 25, y: 0 }); }); test('Can render two points', () => { - const { - lineGeometry: { points }, - indexedGeometryMap, - } = renderedLine; - - expect(points[0]).toEqual(({ - x: 0, - y: 0, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], + const [ + { + value: { points }, }, - styleOverrides: undefined, - value: { - accessor: 'y1', + ] = lines; + + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 10, - mark: null, - datum: [0, 10], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 50, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 5, - mark: null, - datum: [1, 5], - }, - transform: { - x: 25, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + styleOverrides: undefined, + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + datum: [0, 10], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 50, + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 1, + y: 5, + mark: null, + datum: [1, 5], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); + expect(geometriesIndex.size).toEqual(points.length); }); }); describe('Multi series line chart - ordinal', () => { const spec1Id = 'point1'; const spec2Id = 'point2'; - const pointSeriesSpec1: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec1 = MockSeriesSpec.line({ id: spec1Id, groupId: GROUP_ID, seriesType: SeriesTypes.Line, @@ -216,13 +126,10 @@ describe('Rendering points - line', () => { yAccessors: [1], xScaleType: ScaleType.Ordinal, yScaleType: ScaleType.Linear, - }; - const pointSeriesSpec2: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const pointSeriesSpec2 = MockSeriesSpec.line({ id: spec2Id, groupId: GROUP_ID, - seriesType: SeriesTypes.Line, data: [ [0, 20], [1, 10], @@ -231,189 +138,133 @@ describe('Rendering points - line', () => { yAccessors: [1], xScaleType: ScaleType.Ordinal, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec1, pointSeriesSpec2]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let firstLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - let secondLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - firstLine = renderLine( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, - { - enabled: false, - }, - ); - secondLine = renderLine( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], - xScale, - yScales.get(GROUP_ID)!, - 'blue', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, - { - enabled: false, - }, - ); - }); + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec1, pointSeriesSpec2, settings], store); + const { + geometries: { lines }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); test('Can render two ordinal lines', () => { - expect(firstLine.lineGeometry.line).toBe('M0,50L50,75'); - expect(firstLine.lineGeometry.color).toBe('red'); - expect(firstLine.lineGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(firstLine.lineGeometry.seriesIdentifier.specId).toEqual(spec1Id); - expect(firstLine.lineGeometry.transform).toEqual({ x: 25, y: 0 }); + const [{ value: firstLine }, { value: secondLine }] = lines; + expect(firstLine.color).toBe('red'); + expect(firstLine.seriesIdentifier.seriesKeys).toEqual([1]); + expect(firstLine.seriesIdentifier.specId).toEqual(spec1Id); + expect(firstLine.transform).toEqual({ x: 25, y: 0 }); - expect(secondLine.lineGeometry.line).toBe('M0,0L50,50'); - expect(secondLine.lineGeometry.color).toBe('blue'); - expect(secondLine.lineGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(secondLine.lineGeometry.seriesIdentifier.specId).toEqual(spec2Id); - expect(secondLine.lineGeometry.transform).toEqual({ x: 25, y: 0 }); + expect(secondLine.line).toBe('M0,0L50,50'); + expect(secondLine.color).toBe('blue'); + expect(secondLine.seriesIdentifier.seriesKeys).toEqual([1]); + expect(secondLine.seriesIdentifier.specId).toEqual(spec2Id); + expect(secondLine.transform).toEqual({ x: 25, y: 0 }); }); test('can render first spec points', () => { - const { - lineGeometry: { points }, - indexedGeometryMap, - } = firstLine; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - key: 'spec{point1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], + const [ + { + value: { points }, }, - value: { - accessor: 'y1', + ] = lines; + expect(points.length).toEqual(2); + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 10, - mark: null, - datum: [0, 10], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 50, - y: 75, - color: 'red', - radius: 0, - seriesIdentifier: { - specId: spec1Id, - key: 'spec{point1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 5, - mark: null, - datum: [1, 5], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec1), + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + datum: [0, 10], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 50, + y: 75, + color: 'red', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec1), + value: { + accessor: 'y1', + x: 1, + y: 5, + mark: null, + datum: [1, 5], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); + expect(geometriesIndex.size).toEqual(points.length); }); test('can render second spec points', () => { - const { - lineGeometry: { points }, - indexedGeometryMap, - } = secondLine; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 0, - color: 'blue', - radius: 0, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{point2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], + const [ + , + { + value: { points }, }, - value: { - accessor: 'y1', + ] = lines; + expect(points.length).toEqual(2); + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 20, - mark: null, - datum: [0, 20], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 50, - y: 50, - color: 'blue', - radius: 0, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{point2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 10, - mark: null, - datum: [1, 10], - }, - transform: { - x: 25, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + color: 'blue', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec2), + value: { + accessor: 'y1', + x: 0, + y: 20, + mark: null, + datum: [0, 20], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 50, + y: 50, + color: 'blue', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec2), + value: { + accessor: 'y1', + x: 1, + y: 10, + mark: null, + datum: [1, 10], + }, + transform: { + x: 25, + y: 0, + }, + }), + ); + expect(geometriesIndex.size).toEqual(points.length); }); }); describe('Single series line chart - linear', () => { - const pointSeriesSpec: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec = MockSeriesSpec.line({ id: SPEC_ID, groupId: GROUP_ID, - seriesType: SeriesTypes.Line, data: [ [0, 10], [1, 5], @@ -422,109 +273,71 @@ describe('Rendering points - line', () => { yAccessors: [1], xScaleType: ScaleType.Linear, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec, settings], store); + const { + geometries: { lines }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); - beforeEach(() => { - renderedLine = renderLine( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, - { - enabled: false, - }, - ); - }); test('Can render a linear line', () => { - expect(renderedLine.lineGeometry.line).toBe('M0,0L100,50'); - expect(renderedLine.lineGeometry.color).toBe('red'); - expect(renderedLine.lineGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(renderedLine.lineGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); - expect(renderedLine.lineGeometry.transform).toEqual({ x: 0, y: 0 }); + const [{ value: renderedLine }] = lines; + expect(renderedLine.line).toBe('M0,0L100,50'); + expect(renderedLine.color).toBe('red'); + expect(renderedLine.seriesIdentifier.seriesKeys).toEqual([1]); + expect(renderedLine.seriesIdentifier.specId).toEqual(SPEC_ID); + expect(renderedLine.transform).toEqual({ x: 0, y: 0 }); }); test('Can render two points', () => { - const { - lineGeometry: { points }, - indexedGeometryMap, - } = renderedLine; - expect(points[0]).toEqual(({ - x: 0, - y: 0, - color: 'red', - radius: 0, - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 0, - y: 10, - mark: null, - datum: [0, 10], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 50, - color: 'red', - radius: 0, - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 5, - mark: null, - datum: [1, 5], + const [ + { + value: { points }, }, - transform: { + ] = lines; + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + color: 'red', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + datum: [0, 10], + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 50, + color: 'red', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 1, + y: 5, + mark: null, + datum: [1, 5], + }, + }), + ); + expect(geometriesIndex.size).toEqual(points.length); }); }); describe('Multi series line chart - linear', () => { const spec1Id = 'point1'; const spec2Id = 'point2'; - const pointSeriesSpec1: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec1 = MockSeriesSpec.line({ id: spec1Id, groupId: GROUP_ID, - seriesType: SeriesTypes.Line, data: [ [0, 10], [1, 5], @@ -533,13 +346,10 @@ describe('Rendering points - line', () => { yAccessors: [1], xScaleType: ScaleType.Linear, yScaleType: ScaleType.Linear, - }; - const pointSeriesSpec2: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const pointSeriesSpec2 = MockSeriesSpec.line({ id: spec2Id, groupId: GROUP_ID, - seriesType: SeriesTypes.Line, data: [ [0, 20], [1, 10], @@ -548,188 +358,117 @@ describe('Rendering points - line', () => { yAccessors: [1], xScaleType: ScaleType.Linear, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec1, pointSeriesSpec2]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec1, pointSeriesSpec2, settings], store); + const { + geometries: { lines }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); - let firstLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - let secondLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - firstLine = renderLine( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, - { - enabled: false, - }, - ); - secondLine = renderLine( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], - xScale, - yScales.get(GROUP_ID)!, - 'blue', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, - { - enabled: false, - }, - ); - }); test('can render two linear lines', () => { - expect(firstLine.lineGeometry.line).toBe('M0,50L100,75'); - expect(firstLine.lineGeometry.color).toBe('red'); - expect(firstLine.lineGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(firstLine.lineGeometry.seriesIdentifier.specId).toEqual(spec1Id); - expect(firstLine.lineGeometry.transform).toEqual({ x: 0, y: 0 }); + const [{ value: firstLine }, { value: secondLine }] = lines; + expect(firstLine.line).toBe('M0,50L100,75'); + expect(firstLine.color).toBe('red'); + expect(firstLine.seriesIdentifier.seriesKeys).toEqual([1]); + expect(firstLine.seriesIdentifier.specId).toEqual(spec1Id); + expect(firstLine.transform).toEqual({ x: 0, y: 0 }); - expect(secondLine.lineGeometry.line).toBe('M0,0L100,50'); - expect(secondLine.lineGeometry.color).toBe('blue'); - expect(secondLine.lineGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(secondLine.lineGeometry.seriesIdentifier.specId).toEqual(spec2Id); - expect(secondLine.lineGeometry.transform).toEqual({ x: 0, y: 0 }); + expect(secondLine.line).toBe('M0,0L100,50'); + expect(secondLine.color).toBe('blue'); + expect(secondLine.seriesIdentifier.seriesKeys).toEqual([1]); + expect(secondLine.seriesIdentifier.specId).toEqual(spec2Id); + expect(secondLine.transform).toEqual({ x: 0, y: 0 }); }); test('can render first spec points', () => { - const { - lineGeometry: { points }, - indexedGeometryMap, - } = firstLine; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - key: 'spec{point1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 0, - y: 10, - mark: null, - datum: [0, 10], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 75, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - key: 'spec{point1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 5, - mark: null, - datum: [1, 5], + const [ + { + value: { points }, }, - transform: { + ] = lines; + expect(points.length).toEqual(2); + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec1), + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + datum: [0, 10], + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 75, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec1), + value: { + accessor: 'y1', + x: 1, + y: 5, + mark: null, + datum: [1, 5], + }, + }), + ); + expect(geometriesIndex.size).toEqual(points.length); }); test('can render second spec points', () => { - const { - lineGeometry: { points }, - indexedGeometryMap, - } = secondLine; - expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 0, - color: 'blue', - radius: 0, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{point2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 0, - y: 20, - mark: null, - datum: [0, 20], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 50, - color: 'blue', - radius: 0, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{point2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 10, - mark: null, - datum: [1, 10], + const [ + , + { + value: { points }, }, - transform: { + ] = lines; + expect(points.length).toEqual(2); + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + color: 'blue', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec2), + value: { + accessor: 'y1', + x: 0, + y: 20, + mark: null, + datum: [0, 20], + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 50, + color: 'blue', + radius: 0, + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec2), + value: { + accessor: 'y1', + x: 1, + y: 10, + mark: null, + datum: [1, 10], + }, + }), + ); + expect(geometriesIndex.size).toEqual(points.length); }); }); describe('Single series line chart - time', () => { - const pointSeriesSpec: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec = MockSeriesSpec.line({ id: SPEC_ID, groupId: GROUP_ID, - seriesType: SeriesTypes.Line, data: [ [1546300800000, 10], [1546387200000, 5], @@ -738,109 +477,71 @@ describe('Rendering points - line', () => { yAccessors: [1], xScaleType: ScaleType.Time, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec, settings], store); + const { + geometries: { lines }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); - beforeEach(() => { - renderedLine = renderLine( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, - { - enabled: false, - }, - ); - }); test('Can render a time line', () => { - expect(renderedLine.lineGeometry.line).toBe('M0,0L100,50'); - expect(renderedLine.lineGeometry.color).toBe('red'); - expect(renderedLine.lineGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(renderedLine.lineGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); - expect(renderedLine.lineGeometry.transform).toEqual({ x: 0, y: 0 }); + const [{ value: renderedLine }] = lines; + expect(renderedLine.line).toBe('M0,0L100,50'); + expect(renderedLine.color).toBe('red'); + expect(renderedLine.seriesIdentifier.seriesKeys).toEqual([1]); + expect(renderedLine.seriesIdentifier.specId).toEqual(SPEC_ID); + expect(renderedLine.transform).toEqual({ x: 0, y: 0 }); }); test('Can render two points', () => { - const { - lineGeometry: { points }, - indexedGeometryMap, - } = renderedLine; - expect(points[0]).toEqual(({ - x: 0, - y: 0, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1546300800000, - y: 10, - mark: null, - datum: [1546300800000, 10], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1546387200000, - y: 5, - mark: null, - datum: [1546387200000, 5], + const [ + { + value: { points }, }, - transform: { + ] = lines; + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 1546300800000, + y: 10, + mark: null, + datum: [1546300800000, 10], + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 1546387200000, + y: 5, + mark: null, + datum: [1546387200000, 5], + }, + }), + ); + expect(geometriesIndex.size).toEqual(points.length); }); }); describe('Multi series line chart - time', () => { const spec1Id = 'point1'; const spec2Id = 'point2'; - const pointSeriesSpec1: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec1 = MockSeriesSpec.line({ id: spec1Id, groupId: GROUP_ID, - seriesType: SeriesTypes.Line, data: [ [1546300800000, 10], [1546387200000, 5], @@ -849,13 +550,10 @@ describe('Rendering points - line', () => { yAccessors: [1], xScaleType: ScaleType.Time, yScaleType: ScaleType.Linear, - }; - const pointSeriesSpec2: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const pointSeriesSpec2 = MockSeriesSpec.line({ id: spec2Id, groupId: GROUP_ID, - seriesType: SeriesTypes.Line, data: [ [1546300800000, 20], [1546387200000, 10], @@ -864,175 +562,100 @@ describe('Rendering points - line', () => { yAccessors: [1], xScaleType: ScaleType.Time, yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec1, pointSeriesSpec2]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - - let firstLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - let secondLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; + const store = MockStore.default(); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec1, pointSeriesSpec2, settings], store); + const { + geometries: { + lines: [firstLine, secondLine], + }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); - beforeEach(() => { - firstLine = renderLine( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, - { - enabled: false, - }, - ); - secondLine = renderLine( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[1], - xScale, - yScales.get(GROUP_ID)!, - 'blue', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, - { - enabled: false, - }, - ); - }); test('can render first spec points', () => { const { - lineGeometry: { points }, - indexedGeometryMap, + value: { points }, } = firstLine; expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 50, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - key: 'spec{point1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1546300800000, - y: 10, - mark: null, - datum: [1546300800000, 10], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 75, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: spec1Id, - key: 'spec{point1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1546387200000, - y: 5, - mark: null, - datum: [1546387200000, 5], - }, - transform: { + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + y: 50, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec1), + value: { + accessor: 'y1', + x: 1546300800000, + y: 10, + mark: null, + datum: [1546300800000, 10], + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 75, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec1), + value: { + accessor: 'y1', + x: 1546387200000, + y: 5, + mark: null, + datum: [1546387200000, 5], + }, + }), + ); + expect(geometriesIndex.size).toEqual(points.length); }); test('can render second spec points', () => { const { - lineGeometry: { points }, - indexedGeometryMap, + value: { points }, } = secondLine; expect(points.length).toEqual(2); - expect(points[0]).toEqual(({ - x: 0, - y: 0, - radius: 0, - color: 'blue', - seriesIdentifier: { - specId: spec2Id, - key: 'spec{point2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1546300800000, - y: 20, - mark: null, - datum: [1546300800000, 20], - }, - transform: { - x: 0, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 100, - y: 50, - radius: 0, - color: 'blue', - seriesIdentifier: { - specId: spec2Id, - key: 'spec{point2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1546387200000, - y: 10, - mark: null, - datum: [1546387200000, 10], - }, - transform: { + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, y: 0, - }, - } as unknown) as PointGeometry); - expect(indexedGeometryMap.size).toEqual(points.length); + radius: 0, + color: 'blue', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec2), + value: { + accessor: 'y1', + x: 1546300800000, + y: 20, + mark: null, + datum: [1546300800000, 20], + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 100, + y: 50, + radius: 0, + color: 'blue', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec2), + value: { + accessor: 'y1', + x: 1546387200000, + y: 10, + mark: null, + datum: [1546387200000, 10], + }, + }), + ); + expect(geometriesIndex.size).toEqual(points.length); }); }); describe('Single series line chart - y log', () => { - const pointSeriesSpec: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec = MockSeriesSpec.line({ id: SPEC_ID, groupId: GROUP_ID, - seriesType: SeriesTypes.Line, data: [ [0, 10], [1, 5], @@ -1048,58 +671,37 @@ describe('Rendering points - line', () => { yAccessors: [1], xScaleType: ScaleType.Linear, yScaleType: ScaleType.Log, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 90], }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); + const store = MockStore.default({ width: 90, height: 100, top: 0, left: 0 }); + const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); + MockStore.addSpecs([pointSeriesSpec, settings], store); + const { + geometries: { lines }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); - let renderedLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - renderedLine = renderLine( - 0, // not applied any shift, renderGeometries applies it only with mixed charts - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, - { - enabled: false, - }, - ); - }); test('Can render a splitted line', () => { - // expect(renderedLine.lineGeometry.line).toBe('ss'); - expect(renderedLine.lineGeometry.line.split('M').length - 1).toBe(3); - expect(renderedLine.lineGeometry.color).toBe('red'); - expect(renderedLine.lineGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(renderedLine.lineGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); - expect(renderedLine.lineGeometry.transform).toEqual({ x: 0, y: 0 }); + const [{ value: renderedLine }] = lines; + expect(renderedLine.line.split('M').length - 1).toBe(3); + expect(renderedLine.color).toBe('red'); + expect(renderedLine.seriesIdentifier.seriesKeys).toEqual([1]); + expect(renderedLine.seriesIdentifier.specId).toEqual(SPEC_ID); + expect(renderedLine.transform).toEqual({ x: 0, y: 0 }); }); test('Can render points', () => { - const { - lineGeometry: { points }, - indexedGeometryMap, - } = renderedLine; + const [ + { + value: { points }, + }, + ] = lines; // all the points minus the undefined ones on a log scale expect(points.length).toBe(7); // all the points expect null geometries - expect(indexedGeometryMap.size).toEqual(8); - const nullIndexdGeometry = indexedGeometryMap.find(2)!; + expect(geometriesIndex.size).toEqual(8); + const nullIndexdGeometry = geometriesIndex.find(2)!; expect(nullIndexdGeometry).toEqual([]); - const zeroValueIndexdGeometry = indexedGeometryMap.find(5)!; + const zeroValueIndexdGeometry = geometriesIndex.find(5)!; expect(zeroValueIndexdGeometry).toBeDefined(); expect(zeroValueIndexdGeometry.length).toBe(1); // moved to the bottom of the chart @@ -1109,12 +711,9 @@ describe('Rendering points - line', () => { }); }); describe('Remove points datum is not in domain', () => { - const pointSeriesSpec: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const pointSeriesSpec = MockSeriesSpec.line({ id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Line, + // groupId: GROUP_ID, data: [ [0, 0], [1, 1], @@ -1125,179 +724,60 @@ describe('Rendering points - line', () => { yAccessors: [1], xScaleType: ScaleType.Linear, yScaleType: ScaleType.Linear, - }; - const customYDomain = new Map(); - customYDomain.set(GROUP_ID, { - max: 1, - }); - const pointSeriesDomains = computeSeriesDomains([pointSeriesSpec], customYDomain, [], { - max: 2, }); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: 1, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - renderedLine = renderLine( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, + const settings = MockGlobalSpec.settingsNoMargins({ + xDomain: { max: 2 }, + theme: { colors: { vizColors: ['red', 'blue'] } }, + }); + const axis = MockGlobalSpec.axis({ position: Position.Left, hide: true, domain: { max: 1 } }); + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + MockStore.addSpecs([pointSeriesSpec, axis, settings], store); + const { + geometries: { lines }, + geometriesIndex, + } = computeSeriesGeometriesSelector(store.getState()); + test('Can render two points', () => { + const [ { - enabled: false, + value: { points }, }, - ); - }); - test('Can render two points', () => { - const { - lineGeometry: { points }, - indexedGeometryMap, - } = renderedLine; + ] = lines; // will not render the 3rd point that is out of y domain expect(points.length).toBe(2); // will keep the 3rd point as an indexedGeometry - expect(indexedGeometryMap.size).toEqual(3); - expect(points[0]).toEqual(({ - x: 0, - y: 100, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', + expect(geometriesIndex.size).toEqual(3); + expect(points[0]).toEqual( + MockPointGeometry.default({ x: 0, + y: 100, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 0, + y: 0, + mark: null, + datum: [0, 0], + }, + }), + ); + expect(points[1]).toEqual( + MockPointGeometry.default({ + x: 50, y: 0, - mark: null, - datum: [0, 0], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[1]).toEqual(({ - x: 50, - y: 0, - radius: 0, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - value: { - accessor: 'y1', - x: 1, - y: 1, - mark: null, - datum: [1, 1], - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - }); - }); - - describe('Error guards for scaled values', () => { - const pointSeriesSpec: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, - id: SPEC_ID, - groupId: GROUP_ID, - seriesType: SeriesTypes.Line, - data: [ - [0, 10], - [1, 5], - ], - xAccessor: 0, - yAccessors: [1], - xScaleType: ScaleType.Ordinal, - yScaleType: ScaleType.Linear, - }; - const pointSeriesMap = [pointSeriesSpec]; - const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale({ - xDomain: pointSeriesDomains.xDomain, - totalBarsInCluster: pointSeriesMap.length, - range: [0, 100], - }); - const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); - let renderedLine: { - lineGeometry: LineGeometry; - indexedGeometryMap: IndexedGeometryMap; - }; - - beforeEach(() => { - renderedLine = renderLine( - 25, // adding a ideal 25px shift, generally applied by renderGeometries - pointSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], - xScale, - yScales.get(GROUP_ID)!, - 'red', - CurveType.LINEAR, - false, - 0, - LIGHT_THEME.lineSeriesStyle, - { - enabled: false, - }, + radius: 0, + color: 'red', + seriesIdentifier: MockSeriesIdentifier.fromSpec(pointSeriesSpec), + value: { + accessor: 'y1', + x: 1, + y: 1, + mark: null, + datum: [1, 1], + }, + }), ); }); - - describe('xScale values throw error', () => { - beforeAll(() => { - jest.spyOn(xScale, 'scaleOrThrow').mockImplementation(() => { - throw new Error(); - }); - }); - - it('Should have empty line', () => { - const { lineGeometry } = renderedLine; - expect(lineGeometry.line).toBe(''); - expect(lineGeometry.color).toBe('red'); - expect(lineGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(lineGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); - expect(lineGeometry.transform).toEqual({ x: 25, y: 0 }); - }); - }); - - describe('yScale values throw error', () => { - beforeAll(() => { - jest.spyOn(yScales.get(GROUP_ID)!, 'scaleOrThrow').mockImplementation(() => { - throw new Error(); - }); - }); - - it('Should have empty line', () => { - const { lineGeometry } = renderedLine; - expect(lineGeometry.line).toBe(''); - expect(lineGeometry.color).toBe('red'); - expect(lineGeometry.seriesIdentifier.seriesKeys).toEqual([1]); - expect(lineGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); - expect(lineGeometry.transform).toEqual({ x: 25, y: 0 }); - }); - }); }); }); diff --git a/src/chart_types/xy_chart/rendering/rendering.test.ts b/src/chart_types/xy_chart/rendering/rendering.test.ts index 9a33546ae2..d41f737711 100644 --- a/src/chart_types/xy_chart/rendering/rendering.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.test.ts @@ -18,10 +18,9 @@ */ import { LegendItem } from '../../../commons/legend'; -import { MockDataSeries } from '../../../mocks'; +import { MockBarGeometry, MockDataSeries, MockPointGeometry } from '../../../mocks'; import { MockScale } from '../../../mocks/scale'; import { mergePartial, RecursivePartial } from '../../../utils/commons'; -import { BarGeometry, PointGeometry } from '../../../utils/geometry'; import { BarSeriesStyle, SharedGeometryStateStyle, PointStyle } from '../../../utils/themes/theme'; import { DataSeriesDatum, XYChartSeriesIdentifier } from '../utils/series'; import { @@ -53,7 +52,7 @@ describe('Rendering utils', () => { }, }; - const geometry: BarGeometry = { + const geometry = MockBarGeometry.default({ color: 'red', seriesIdentifier: { specId: 'id', @@ -74,7 +73,7 @@ describe('Rendering utils', () => { width: 10, height: 10, seriesStyle, - }; + }); expect(isPointOnGeometry(0, 0, geometry)).toBe(true); expect(isPointOnGeometry(10, 10, geometry)).toBe(true); expect(isPointOnGeometry(0, 10, geometry)).toBe(true); @@ -84,7 +83,7 @@ describe('Rendering utils', () => { expect(isPointOnGeometry(11, 11, geometry)).toBe(false); }); test('check if point is on point geometry', () => { - const geometry: PointGeometry = { + const geometry = MockPointGeometry.default({ color: 'red', seriesIdentifier: { specId: 'id', @@ -107,7 +106,7 @@ describe('Rendering utils', () => { x: 0, y: 0, radius: 10, - }; + }); // with buffer expect(isPointOnGeometry(10, 10, geometry, 10)).toBe(true); expect(isPointOnGeometry(20, 20, geometry, 5)).toBe(false); @@ -169,36 +168,36 @@ describe('Rendering utils', () => { }; it('no highlighted elements', () => { - const defaultStyle = getGeometryStateStyle(seriesIdentifier, null, sharedThemeStyle); + const defaultStyle = getGeometryStateStyle(seriesIdentifier, sharedThemeStyle); expect(defaultStyle).toBe(sharedThemeStyle.default); }); it('should equal highlighted opacity', () => { - const highlightedStyle = getGeometryStateStyle(seriesIdentifier, highlightedLegendItem, sharedThemeStyle); + const highlightedStyle = getGeometryStateStyle(seriesIdentifier, sharedThemeStyle, highlightedLegendItem); expect(highlightedStyle).toBe(sharedThemeStyle.highlighted); }); it('should equal unhighlighted when not highlighted item', () => { - const unhighlightedStyle = getGeometryStateStyle(seriesIdentifier, unhighlightedLegendItem, sharedThemeStyle); + const unhighlightedStyle = getGeometryStateStyle(seriesIdentifier, sharedThemeStyle, unhighlightedLegendItem); expect(unhighlightedStyle).toBe(sharedThemeStyle.unhighlighted); }); it('should equal custom spec highlighted opacity', () => { - const customHighlightedStyle = getGeometryStateStyle(seriesIdentifier, highlightedLegendItem, sharedThemeStyle); + const customHighlightedStyle = getGeometryStateStyle(seriesIdentifier, sharedThemeStyle, highlightedLegendItem); expect(customHighlightedStyle).toBe(sharedThemeStyle.highlighted); }); it('unhighlighted elements remain unchanged with custom opacity', () => { const customUnhighlightedStyle = getGeometryStateStyle( seriesIdentifier, - unhighlightedLegendItem, sharedThemeStyle, + unhighlightedLegendItem, ); expect(customUnhighlightedStyle).toBe(sharedThemeStyle.unhighlighted); }); it('has individual highlight', () => { - const hasIndividualHighlight = getGeometryStateStyle(seriesIdentifier, null, sharedThemeStyle, { + const hasIndividualHighlight = getGeometryStateStyle(seriesIdentifier, sharedThemeStyle, undefined, { hasHighlight: true, hasGeometryHover: true, }); @@ -206,7 +205,7 @@ describe('Rendering utils', () => { }); it('no highlight', () => { - const noHighlight = getGeometryStateStyle(seriesIdentifier, null, sharedThemeStyle, { + const noHighlight = getGeometryStateStyle(seriesIdentifier, sharedThemeStyle, undefined, { hasHighlight: false, hasGeometryHover: true, }); @@ -214,7 +213,7 @@ describe('Rendering utils', () => { }); it('no geometry hover', () => { - const noHover = getGeometryStateStyle(seriesIdentifier, null, sharedThemeStyle, { + const noHover = getGeometryStateStyle(seriesIdentifier, sharedThemeStyle, undefined, { hasHighlight: true, hasGeometryHover: false, }); diff --git a/src/chart_types/xy_chart/rendering/rendering.ts b/src/chart_types/xy_chart/rendering/rendering.ts index 805e89ffc4..665470cc96 100644 --- a/src/chart_types/xy_chart/rendering/rendering.ts +++ b/src/chart_types/xy_chart/rendering/rendering.ts @@ -27,6 +27,7 @@ import { MarkBuffer, StackMode } from '../../../specs'; import { CanvasTextBBoxCalculator } from '../../../utils/bbox/canvas_text_bbox_calculator'; import { mergePartial, Color, getDistance } from '../../../utils/commons'; import { CurveType, getCurveFactory } from '../../../utils/curves'; +import { Dimensions } from '../../../utils/dimensions'; import { PointGeometry, BarGeometry, @@ -227,13 +228,13 @@ function renderPoints( dataSeries: DataSeries, xScale: Scale, yScale: Scale, + panel: Dimensions, color: Color, lineStyle: LineStyle, hasY0Accessors: boolean, markSizeOptions: MarkSizeOptions, styleAccessor?: PointStyleAccessor, spatial = false, - stackMode?: StackMode, ): { pointGeometries: PointGeometry[]; indexedGeometryMap: IndexedGeometryMap; @@ -281,13 +282,15 @@ function renderPoints( if (y === null) { return acc; } - const originalY = getDatumYValue(datum, index === 0, hasY0Accessors, stackMode); + const originalY = getDatumYValue(datum, index === 0, hasY0Accessors, dataSeries.stackMode); const seriesIdentifier: XYChartSeriesIdentifier = { key: dataSeries.key, specId: dataSeries.specId, yAccessor: dataSeries.yAccessor, splitAccessors: dataSeries.splitAccessors, seriesKeys: dataSeries.seriesKeys, + smVerticalAccessorValue: dataSeries.smVerticalAccessorValue, + smHorizontalAccessorValue: dataSeries.smHorizontalAccessorValue, }; const styleOverrides = getPointStyleOverrides(datum, seriesIdentifier, styleAccessor); const pointGeometry: PointGeometry = { @@ -308,6 +311,7 @@ function renderPoints( }, seriesIdentifier, styleOverrides, + panel, }; indexedGeometryMap.set(pointGeometry, geometryType); // use the geometry only if the yDatum in contained in the current yScale domain @@ -362,6 +366,7 @@ export function renderBars( dataSeries: DataSeries, xScale: Scale, yScale: Scale, + panel: Dimensions, color: Color, sharedSeriesStyle: BarSeriesStyle, displayValueSettings?: DisplayValueSpec, @@ -416,7 +421,6 @@ export function renderBars( if (y === null || y0Scaled === null) { return; } - let height = y0Scaled - y; // handle minBarHeight adjustment @@ -492,6 +496,8 @@ export function renderBars( yAccessor: dataSeries.yAccessor, splitAccessors: dataSeries.splitAccessors, seriesKeys: dataSeries.seriesKeys, + smHorizontalAccessorValue: dataSeries.smHorizontalAccessorValue, + smVerticalAccessorValue: dataSeries.smVerticalAccessorValue, }; const seriesStyle = getBarStyleOverrides(datum, seriesIdentifier, sharedSeriesStyle, styleAccessor); @@ -499,7 +505,11 @@ export function renderBars( const barGeometry: BarGeometry = { displayValue, x, - y, // top most value + y, + transform: { + x: 0, + y: 0, + }, width, height, color, @@ -512,6 +522,7 @@ export function renderBars( }, seriesIdentifier, seriesStyle, + panel, }; indexedGeometryMap.set(barGeometry); barGeometries.push(barGeometry); @@ -531,6 +542,7 @@ export function renderLine( dataSeries: DataSeries, xScale: Scale, yScale: Scale, + panel: Dimensions, color: Color, curve: CurveType, hasY0Accessors: boolean, @@ -544,7 +556,6 @@ export function renderLine( indexedGeometryMap: IndexedGeometryMap; } { const isLogScale = isLogarithmicScale(yScale); - const pathGenerator = line() .x(({ x }) => xScale.scaleOrThrow(x) - xScaleOffset) .y((datum) => { @@ -562,14 +573,13 @@ export function renderLine( return yValue !== null && !(isLogScale && yValue <= 0) && xScale.isValueInDomain(datum.x); }) .curve(getCurveFactory(curve)); - const y = 0; - const x = shift; const { pointGeometries, indexedGeometryMap } = renderPoints( shift - xScaleOffset, dataSeries, xScale, yScale, + panel, color, seriesStyle.line, hasY0Accessors, @@ -592,8 +602,8 @@ export function renderLine( points: pointGeometries, color, transform: { - x, - y, + x: shift, + y: 0, }, seriesIdentifier: { key: dataSeries.key, @@ -601,6 +611,8 @@ export function renderLine( yAccessor: dataSeries.yAccessor, splitAccessors: dataSeries.splitAccessors, seriesKeys: dataSeries.seriesKeys, + smHorizontalAccessorValue: dataSeries.smHorizontalAccessorValue, + smVerticalAccessorValue: dataSeries.smVerticalAccessorValue, }, seriesLineStyle: seriesStyle.line, seriesPointStyle: seriesStyle.point, @@ -620,7 +632,9 @@ export function renderBubble( xScale: Scale, yScale: Scale, color: Color, + panel: Dimensions, hasY0Accessors: boolean, + xScaleOffset: number, seriesStyle: BubbleSeriesStyle, markSizeOptions: MarkSizeOptions, isMixedChart: boolean, @@ -630,10 +644,11 @@ export function renderBubble( indexedGeometryMap: IndexedGeometryMap; } { const { pointGeometries, indexedGeometryMap } = renderPoints( - shift, + shift - xScaleOffset, dataSeries, xScale, yScale, + panel, color, seriesStyle.point, hasY0Accessors, @@ -651,6 +666,8 @@ export function renderBubble( yAccessor: dataSeries.yAccessor, splitAccessors: dataSeries.splitAccessors, seriesKeys: dataSeries.seriesKeys, + smHorizontalAccessorValue: dataSeries.smHorizontalAccessorValue, + smVerticalAccessorValue: dataSeries.smVerticalAccessorValue, }, seriesPointStyle: seriesStyle.point, }; @@ -667,6 +684,7 @@ export function renderArea( dataSeries: DataSeries, xScale: Scale, yScale: Scale, + panel: Dimensions, color: Color, curve: CurveType, hasY0Accessors: boolean, @@ -676,12 +694,12 @@ export function renderArea( isStacked = false, pointStyleAccessor?: PointStyleAccessor, hasFit?: boolean, - stackMode?: StackMode, ): { areaGeometry: AreaGeometry; indexedGeometryMap: IndexedGeometryMap; } { const isLogScale = isLogarithmicScale(yScale); + const pathGenerator = area() .x(({ x }) => xScale.scaleOrThrow(x) - xScaleOffset) .y1((datum) => { @@ -693,11 +711,7 @@ export function renderArea( return yScale.isInverted ? yScale.range[1] : yScale.range[0]; }) .y0(({ y0 }) => { - if (y0 === null || (isLogScale && y0 <= 0)) { - return yScale.range[0]; - } - - return yScale.scaleOrThrow(y0); + return y0 === null || (isLogScale && y0 <= 0) ? yScale.range[0] : yScale.scaleOrThrow(y0); }) .defined((datum) => { const yValue = getYValue(datum); @@ -739,13 +753,13 @@ export function renderArea( dataSeries, xScale, yScale, + panel, color, seriesStyle.line, hasY0Accessors, markSizeOptions, pointStyleAccessor, false, - stackMode, ); let areaPath: string; @@ -772,6 +786,8 @@ export function renderArea( yAccessor: dataSeries.yAccessor, splitAccessors: dataSeries.splitAccessors, seriesKeys: dataSeries.seriesKeys, + smHorizontalAccessorValue: dataSeries.smHorizontalAccessorValue, + smVerticalAccessorValue: dataSeries.smVerticalAccessorValue, }, seriesAreaStyle: seriesStyle.area, seriesAreaLineStyle: seriesStyle.line, @@ -800,17 +816,18 @@ export function isDatumFilled({ filled, initialY1 }: DataSeriesDatum) { * @param dataset * @param xScale * @param xScaleOffset + * @param panel * @internal */ export function getClippedRanges(dataset: DataSeriesDatum[], xScale: Scale, xScaleOffset: number): ClippedRanges { let firstNonNullX: number | null = null; let hasNull = false; - return dataset.reduce((acc, data) => { const xScaled = xScale.scale(data.x); if (xScaled === null) { return acc; } + const xValue = xScaled - xScaleOffset + xScale.bandwidth / 2; if (isDatumFilled(data)) { @@ -838,13 +855,13 @@ export function getClippedRanges(dataset: DataSeriesDatum[], xScale: Scale, xSca /** @internal */ export function getGeometryStateStyle( seriesIdentifier: XYChartSeriesIdentifier, - highlightedLegendItem: LegendItem | null, sharedGeometryStyle: SharedGeometryStateStyle, + highlightedLegendItem?: LegendItem, individualHighlight?: { [key: string]: boolean }, ): GeometryStateStyle { const { default: defaultStyles, highlighted, unhighlighted } = sharedGeometryStyle; - if (highlightedLegendItem != null) { + if (highlightedLegendItem) { const isPartOfHighlightedSeries = seriesIdentifier.key === highlightedLegendItem.seriesIdentifier.key; return isPartOfHighlightedSeries ? highlighted : unhighlighted; @@ -870,17 +887,18 @@ export function isPointOnGeometry( ) { const { x, y } = indexedGeometry; if (isPointGeometry(indexedGeometry)) { - const { radius, transform } = indexedGeometry; + const { radius } = indexedGeometry; const distance = getDistance( { x: xCoordinate, y: yCoordinate, }, { - x: x + transform.x, + x, y, }, ); + const radiusBuffer = typeof buffer === 'number' ? buffer : buffer(radius); if (radiusBuffer === Infinity) { diff --git a/src/chart_types/xy_chart/state/chart_state.interactions.test.ts b/src/chart_types/xy_chart/state/chart_state.interactions.test.ts index 746fcb23c5..8e07720558 100644 --- a/src/chart_types/xy_chart/state/chart_state.interactions.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.interactions.test.ts @@ -165,7 +165,7 @@ describe('Chart state pointer interactions', () => { const { geometries } = computeSeriesGeometriesSelector(store.getState()); expect(geometries).toBeDefined(); expect(geometries.bars).toBeDefined(); - expect(geometries.bars.length).toBe(2); + expect(geometries.bars[0].value.length).toBe(2); }); test('can convert/limit mouse pointer positions relative to chart projection', () => { @@ -398,7 +398,7 @@ function mouseOverTestSuite(scaleType: XScaleType) { expect(tooltipInfo.tooltip.values).toEqual([]); store.dispatch(onPointerMove({ x: chartLeft + 0, y: chartTop + 0 }, 0)); let projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); - expect(projectedPointerPosition).toEqual({ x: 0, y: 0 }); + expect(projectedPointerPosition).toMatchObject({ x: 0, y: 0 }); const cursorBandPosition = getCursorBandPositionSelector(store.getState()); expect(cursorBandPosition).toBeDefined(); expect(cursorBandPosition?.left).toBe(chartLeft + 0); @@ -420,18 +420,21 @@ function mouseOverTestSuite(scaleType: XScaleType) { datum: [0, 10], }, { - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', + key: + 'groupId{group_1}spec{spec_1}yAccessor{1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', seriesKeys: [1], specId: 'spec_1', splitAccessors: new Map(), yAccessor: 1, + smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', + smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', }, ], ]); store.dispatch(onPointerMove({ x: chartLeft - 1, y: chartTop - 1 }, 1)); projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); - expect(projectedPointerPosition).toEqual({ x: -1, y: -1 }); + expect(projectedPointerPosition).toMatchObject({ x: -1, y: -1 }); isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible.visible).toBe(false); tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); @@ -444,7 +447,7 @@ function mouseOverTestSuite(scaleType: XScaleType) { test('can hover bottom-left corner of the first bar', () => { store.dispatch(onPointerMove({ x: chartLeft + 0, y: chartTop + 89 }, 0)); let projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); - expect(projectedPointerPosition).toEqual({ x: 0, y: 89 }); + expect(projectedPointerPosition).toMatchObject({ x: 0, y: 89 }); const cursorBandPosition = getCursorBandPositionSelector(store.getState()); expect(cursorBandPosition).toBeDefined(); expect(cursorBandPosition?.left).toBe(chartLeft + 0); @@ -466,17 +469,21 @@ function mouseOverTestSuite(scaleType: XScaleType) { datum: [0, 10], }, { - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', + key: + 'groupId{group_1}spec{spec_1}yAccessor{1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', seriesKeys: [1], specId: 'spec_1', splitAccessors: new Map(), yAccessor: 1, + + smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', + smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', }, ], ]); store.dispatch(onPointerMove({ x: chartLeft - 1, y: chartTop + 89 }, 1)); projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); - expect(projectedPointerPosition).toEqual({ x: -1, y: 89 }); + expect(projectedPointerPosition).toMatchObject({ x: -1, y: 89 }); isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible.visible).toBe(false); tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); @@ -493,7 +500,7 @@ function mouseOverTestSuite(scaleType: XScaleType) { } store.dispatch(onPointerMove({ x: chartLeft + 44 + scaleOffset, y: chartTop + 0 }, 0)); let projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); - expect(projectedPointerPosition).toEqual({ x: 44 + scaleOffset, y: 0 }); + expect(projectedPointerPosition).toMatchObject({ x: 44 + scaleOffset, y: 0 }); let cursorBandPosition = getCursorBandPositionSelector(store.getState()); expect(cursorBandPosition).toBeDefined(); expect(cursorBandPosition?.left).toBe(chartLeft + 0); @@ -515,18 +522,21 @@ function mouseOverTestSuite(scaleType: XScaleType) { datum: [0, 10], }, { - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', + key: + 'groupId{group_1}spec{spec_1}yAccessor{1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', seriesKeys: [1], specId: 'spec_1', splitAccessors: new Map(), yAccessor: 1, + smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', + smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', }, ], ]); store.dispatch(onPointerMove({ x: chartLeft + 45 + scaleOffset, y: chartTop + 0 }, 1)); projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); - expect(projectedPointerPosition).toEqual({ x: 45 + scaleOffset, y: 0 }); + expect(projectedPointerPosition).toMatchObject({ x: 45 + scaleOffset, y: 0 }); cursorBandPosition = getCursorBandPositionSelector(store.getState()); expect(cursorBandPosition).toBeDefined(); expect(cursorBandPosition?.left).toBe(chartLeft + 45); @@ -547,7 +557,7 @@ function mouseOverTestSuite(scaleType: XScaleType) { } store.dispatch(onPointerMove({ x: chartLeft + 44 + scaleOffset, y: chartTop + 89 }, 0)); let projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); - expect(projectedPointerPosition).toEqual({ x: 44 + scaleOffset, y: 89 }); + expect(projectedPointerPosition).toMatchObject({ x: 44 + scaleOffset, y: 89 }); let cursorBandPosition = getCursorBandPositionSelector(store.getState()); expect(cursorBandPosition).toBeDefined(); expect(cursorBandPosition?.left).toBe(chartLeft + 0); @@ -569,18 +579,21 @@ function mouseOverTestSuite(scaleType: XScaleType) { datum: [(spec.data[0] as Array)[0], (spec.data[0] as Array)[1]], }, { - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', + key: + 'groupId{group_1}spec{spec_1}yAccessor{1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', seriesKeys: [1], specId: 'spec_1', splitAccessors: new Map(), yAccessor: 1, + smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', + smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', }, ], ]); store.dispatch(onPointerMove({ x: chartLeft + 45 + scaleOffset, y: chartTop + 89 }, 1)); projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); - expect(projectedPointerPosition).toEqual({ x: 45 + scaleOffset, y: 89 }); + expect(projectedPointerPosition).toMatchObject({ x: 45 + scaleOffset, y: 89 }); cursorBandPosition = getCursorBandPositionSelector(store.getState()); expect(cursorBandPosition).toBeDefined(); expect(cursorBandPosition?.left).toBe(chartLeft + 45); @@ -602,11 +615,14 @@ function mouseOverTestSuite(scaleType: XScaleType) { datum: [(spec.data[1] as Array)[0], (spec.data[1] as Array)[1]], }, { - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', + key: + 'groupId{group_1}spec{spec_1}yAccessor{1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', seriesKeys: [1], specId: 'spec_1', splitAccessors: new Map(), yAccessor: 1, + smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', + smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', }, ], ]); @@ -625,7 +641,7 @@ function mouseOverTestSuite(scaleType: XScaleType) { store.dispatch(onPointerMove({ x: chartLeft + 89, y: chartTop + 0 }, 0)); const projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); - expect(projectedPointerPosition).toEqual({ x: 89, y: 0 }); + expect(projectedPointerPosition).toMatchObject({ x: 89, y: 0 }); const cursorBandPosition = getCursorBandPositionSelector(store.getState()); expect(cursorBandPosition).toBeDefined(); expect(cursorBandPosition?.left).toBe(chartLeft + 45); @@ -678,7 +694,7 @@ function mouseOverTestSuite(scaleType: XScaleType) { store.dispatch(onPointerMove({ x: chartLeft + 89, y: chartTop + 89 }, 0)); const projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); // store.setCursorPosition(chartLeft + 99, chartTop + 99); - expect(projectedPointerPosition).toEqual({ x: 89, y: 89 }); + expect(projectedPointerPosition).toMatchObject({ x: 89, y: 89 }); const cursorBandPosition = getCursorBandPositionSelector(store.getState()); expect(cursorBandPosition).toBeDefined(); expect(cursorBandPosition?.left).toBe(chartLeft + 45); @@ -699,11 +715,14 @@ function mouseOverTestSuite(scaleType: XScaleType) { datum: [1, 5], }, { - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', + key: + 'groupId{group_1}spec{spec_1}yAccessor{1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', seriesKeys: [1], specId: 'spec_1', splitAccessors: new Map(), yAccessor: 1, + smHorizontalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', + smVerticalAccessorValue: '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__', }, ], ]); diff --git a/src/chart_types/xy_chart/state/chart_state.test.ts b/src/chart_types/xy_chart/state/chart_state.test.ts index f1e288419c..9aaa1a3fc2 100644 --- a/src/chart_types/xy_chart/state/chart_state.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.test.ts @@ -19,12 +19,13 @@ import { ChartTypes } from '../..'; import { LegendItem } from '../../../commons/legend'; +import { MockBarGeometry } from '../../../mocks'; import { ScaleContinuous, ScaleBand } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; import { SpecTypes, TooltipType } from '../../../specs/constants'; import { TooltipValue } from '../../../specs/settings'; import { Position, RecursivePartial } from '../../../utils/commons'; -import { IndexedGeometry, GeometryValue, BandedAccessorType } from '../../../utils/geometry'; +import { GeometryValue, BandedAccessorType } from '../../../utils/geometry'; import { AxisId } from '../../../utils/ids'; import { AxisStyle } from '../../../utils/themes/theme'; import { AxisTicksDimensions, isDuplicateAxis } from '../utils/axis_utils'; @@ -120,6 +121,7 @@ describe.skip('Chart Store', () => { maxLabelBboxHeight: 1, maxLabelTextWidth: 1, maxLabelTextHeight: 1, + isHidden: false, }; let tickMap: Map; let specMap: AxisSpec[]; @@ -911,7 +913,7 @@ describe.skip('Chart Store', () => { padding: 2, }, }; - const geom1: IndexedGeometry = { + const geom1 = MockBarGeometry.default({ color: 'red', seriesIdentifier: { specId: 'specId1', @@ -932,8 +934,8 @@ describe.skip('Chart Store', () => { width: 0, height: 0, seriesStyle: barStyle, - }; - const geom2: IndexedGeometry = { + }); + const geom2 = MockBarGeometry.default({ color: 'blue', seriesIdentifier: { specId: 'specId2', @@ -954,7 +956,7 @@ describe.skip('Chart Store', () => { width: 0, height: 0, seriesStyle: barStyle, - }; + }); const clickListener = jest.fn((): void => undefined); store.setOnElementClickListener(clickListener); @@ -1129,7 +1131,7 @@ describe.skip('Chart Store', () => { store.cursorPosition.x = 10; store.cursorPosition.y = 10; store.onBrushEndListener = brushEndListener; - const geom1: IndexedGeometry = { + const geom1 = MockBarGeometry.default({ color: 'red', seriesIdentifier: { specId: 'specId1', @@ -1166,7 +1168,7 @@ describe.skip('Chart Store', () => { padding: 2, }, }, - }; + }); store.highlightedGeometries.replace([geom1]); expect(store.chartCursor.get()).toBe('crosshair'); store.onElementClickListener = jest.fn(); diff --git a/src/chart_types/xy_chart/state/chart_state.timescales.test.ts b/src/chart_types/xy_chart/state/chart_state.timescales.test.ts index 282963a0d3..bb4be94f85 100644 --- a/src/chart_types/xy_chart/state/chart_state.timescales.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.timescales.test.ts @@ -85,7 +85,7 @@ describe('Render chart', () => { expect(geometries).toBeDefined(); expect(geometries.lines).toBeDefined(); expect(geometries.lines.length).toBe(1); - expect(geometries.lines[0].points.length).toBe(3); + expect(geometries.lines[0].value.points.length).toBe(3); }); test('check mouse position correctly return inverted value', () => { store.dispatch(onPointerMove({ x: 15, y: 10 }, 0)); // check first valid tooltip @@ -159,7 +159,7 @@ describe('Render chart', () => { expect(geometries).toBeDefined(); expect(geometries.lines).toBeDefined(); expect(geometries.lines.length).toBe(1); - expect(geometries.lines[0].points.length).toBe(3); + expect(geometries.lines[0].value.points.length).toBe(3); }); test('check mouse position correctly return inverted value', () => { store.dispatch(onPointerMove({ x: 15, y: 10 }, 0)); // check first valid tooltip @@ -232,7 +232,7 @@ describe('Render chart', () => { expect(geometries).toBeDefined(); expect(geometries.lines).toBeDefined(); expect(geometries.lines.length).toBe(1); - expect(geometries.lines[0].points.length).toBe(3); + expect(geometries.lines[0].value.points.length).toBe(3); }); test('check scale values', () => { const xValues = [date1, date2, date3]; diff --git a/src/chart_types/xy_chart/state/selectors/compute_annotations.ts b/src/chart_types/xy_chart/state/selectors/compute_annotations.ts index beea55da97..b151d68aa9 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_annotations.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_annotations.ts @@ -26,6 +26,7 @@ import { AnnotationDimensions } from '../../annotations/types'; import { computeAnnotationDimensions } from '../../annotations/utils'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; import { computeSeriesGeometriesSelector } from './compute_series_geometries'; +import { computeSmallMultipleScalesSelector } from './compute_small_multiple_scales'; import { getAxisSpecsSelector, getAnnotationSpecsSelector } from './get_specs'; import { isHistogramModeEnabledSelector } from './is_histogram_mode_enabled'; @@ -38,6 +39,7 @@ export const computeAnnotationDimensionsSelector = createCachedSelector( computeSeriesGeometriesSelector, getAxisSpecsSelector, isHistogramModeEnabledSelector, + computeSmallMultipleScalesSelector, ], ( annotationSpecs, @@ -46,6 +48,7 @@ export const computeAnnotationDimensionsSelector = createCachedSelector( { scales: { yScales, xScale } }, axesSpecs, isHistogramMode, + smallMultipleScales, ): Map => computeAnnotationDimensions( annotationSpecs, @@ -55,5 +58,6 @@ export const computeAnnotationDimensionsSelector = createCachedSelector( xScale, axesSpecs, isHistogramMode, + smallMultipleScales, ), )(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/compute_axis_visible_ticks.ts b/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts similarity index 82% rename from src/chart_types/xy_chart/state/selectors/compute_axis_visible_ticks.ts rename to src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts index 8e764b0386..2d1640e86a 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_axis_visible_ticks.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts @@ -22,12 +22,12 @@ import createCachedSelector from 're-reselect'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { Dimensions } from '../../../../utils/dimensions'; -import { AxisId } from '../../../../utils/ids'; -import { getAxisTicksPositions, AxisTick, AxisLinePosition, defaultTickFormatter } from '../../utils/axis_utils'; +import { getAxesGeometries, AxisGeometry, defaultTickFormatter } from '../../utils/axis_utils'; +import { getPanelSize } from '../../utils/panel'; import { computeAxisTicksDimensionsSelector } from './compute_axis_ticks_dimensions'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; import { computeSeriesDomainsSelector } from './compute_series_domains'; +import { computeSmallMultipleScalesSelector } from './compute_small_multiple_scales'; import { countBarsInClusterSelector } from './count_bars_in_cluster'; import { getAxesStylesSelector } from './get_axis_styles'; import { getBarPaddingsSelector } from './get_bar_paddings'; @@ -35,14 +35,7 @@ import { getAxisSpecsSelector, getSeriesSpecsSelector } from './get_specs'; import { isHistogramModeEnabledSelector } from './is_histogram_mode_enabled'; /** @internal */ -export interface AxisVisibleTicks { - axisPositions: Map; - axisTicks: Map; - axisVisibleTicks: Map; - axisGridLinesPositions: Map; -} -/** @internal */ -export const computeAxisVisibleTicksSelector = createCachedSelector( +export const computeAxesGeometriesSelector = createCachedSelector( [ computeChartDimensionsSelector, getChartThemeSelector, @@ -55,6 +48,7 @@ export const computeAxisVisibleTicksSelector = createCachedSelector( isHistogramModeEnabledSelector, getBarPaddingsSelector, getSeriesSpecsSelector, + computeSmallMultipleScalesSelector, ], ( chartDimensions, @@ -68,10 +62,13 @@ export const computeAxisVisibleTicksSelector = createCachedSelector( isHistogramMode, barsPadding, seriesSpecs, - ): AxisVisibleTicks => { + scales, + ): AxisGeometry[] => { const fallBackTickFormatter = seriesSpecs.find(({ tickFormat }) => tickFormat)?.tickFormat ?? defaultTickFormatter; const { xDomain, yDomain } = seriesDomainsAndData; - return getAxisTicksPositions( + const panel = getPanelSize(scales); + + return getAxesGeometries( chartDimensions, chartTheme, settingsSpec.rotation, @@ -80,6 +77,7 @@ export const computeAxisVisibleTicksSelector = createCachedSelector( axesStyles, xDomain, yDomain, + panel, totalBarsInCluster, isHistogramMode, fallBackTickFormatter, diff --git a/src/chart_types/xy_chart/state/selectors/compute_grid_lines.ts b/src/chart_types/xy_chart/state/selectors/compute_grid_lines.ts new file mode 100644 index 0000000000..646bdf2747 --- /dev/null +++ b/src/chart_types/xy_chart/state/selectors/compute_grid_lines.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import createCachedSelector from 're-reselect'; + +import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; +import { getGridLines, LinesGrid } from '../../utils/grid_lines'; +import { computeAxesGeometriesSelector } from './compute_axes_geometries'; +import { computeSmallMultipleScalesSelector } from './compute_small_multiple_scales'; +import { getAxisSpecsSelector } from './get_specs'; + +/** @internal */ +export const computePerPanelGridLinesSelector = createCachedSelector( + [getAxisSpecsSelector, getChartThemeSelector, computeAxesGeometriesSelector, computeSmallMultipleScalesSelector], + (axesSpecs, chartTheme, axesGeoms, scales): Array => { + return getGridLines(axesSpecs, axesGeoms, chartTheme.axes, scales); + }, +)(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/compute_panels.ts b/src/chart_types/xy_chart/state/selectors/compute_panels.ts new file mode 100644 index 0000000000..13263523a7 --- /dev/null +++ b/src/chart_types/xy_chart/state/selectors/compute_panels.ts @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import createCachedSelector from 're-reselect'; + +import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { Size } from '../../../../utils/dimensions'; +import { getPanelSize } from '../../utils/panel'; +import { PerPanelMap, getPerPanelMap } from '../../utils/panel_utils'; +import { computeSmallMultipleScalesSelector } from './compute_small_multiple_scales'; + +/** @internal */ +export type PanelGeoms = Array; + +/** @internal */ +export const computePanelsSelectors = createCachedSelector( + [computeSmallMultipleScalesSelector], + (scales): PanelGeoms => { + const panelSize = getPanelSize(scales); + return getPerPanelMap(scales, () => panelSize); + }, +)(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/compute_per_panel_axes_geoms.ts b/src/chart_types/xy_chart/state/selectors/compute_per_panel_axes_geoms.ts new file mode 100644 index 0000000000..93c7854253 --- /dev/null +++ b/src/chart_types/xy_chart/state/selectors/compute_per_panel_axes_geoms.ts @@ -0,0 +1,73 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import createCachedSelector from 're-reselect'; + +import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { isHorizontalAxis, isVerticalAxis } from '../../utils/axis_type_utils'; +import { AxisGeometry } from '../../utils/axis_utils'; +import { PerPanelMap, getPerPanelMap } from '../../utils/panel_utils'; +import { computeAxesGeometriesSelector } from './compute_axes_geometries'; +import { computeSmallMultipleScalesSelector } from './compute_small_multiple_scales'; + +/** @internal */ +export type PerPanelAxisGeoms = { + axesGeoms: AxisGeometry[]; +} & PerPanelMap; + +/** @internal */ +export const computePerPanelAxesGeomsSelector = createCachedSelector( + [computeAxesGeometriesSelector, computeSmallMultipleScalesSelector], + (axesGeoms, scales): Array => { + const { horizontal, vertical } = scales; + return getPerPanelMap(scales, (anchor, h, v) => { + const lastLine = horizontal.domain.includes(h) && vertical.domain[vertical.domain.length - 1] === v; + const firstColumn = horizontal.domain[0] === h; + if (firstColumn || lastLine) { + return { + axesGeoms: axesGeoms + .filter(({ axis: { position } }) => { + if (firstColumn && lastLine) { + return true; + } + return firstColumn ? isVerticalAxis(position) : isHorizontalAxis(position); + }) + .map((geom) => { + const { + axis: { position, title }, + } = geom; + const panelTitle = isVerticalAxis(position) ? `${v}` : `${h}`; + const useSmallMultiplePanelTitles = isVerticalAxis(position) + ? vertical.domain.length > 1 + : horizontal.domain.length > 1; + return { + ...geom, + axis: { + ...geom.axis, + title: useSmallMultiplePanelTitles ? panelTitle : title, + }, + }; + }), + }; + } + + return null; + }); + }, +)(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts b/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts index f3bd0fe840..12f3edc396 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts @@ -24,16 +24,22 @@ import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { SeriesDomainsAndData } from '../utils/types'; import { computeSeriesDomains } from '../utils/utils'; -import { getSeriesSpecsSelector } from './get_specs'; +import { getSeriesSpecsSelector, getSmallMultiplesIndexOrderSelector } from './get_specs'; import { mergeYCustomDomainsByGroupIdSelector } from './merge_y_custom_domains'; const getDeselectedSeriesSelector = (state: GlobalChartState) => state.interactions.deselectedDataSeries; /** @internal */ export const computeSeriesDomainsSelector = createCachedSelector( - [getSeriesSpecsSelector, mergeYCustomDomainsByGroupIdSelector, getDeselectedSeriesSelector, getSettingsSpecSelector], - (seriesSpecs, customYDomainsByGroupId, deselectedDataSeries, settingsSpec): SeriesDomainsAndData => { - const domains = computeSeriesDomains( + [ + getSeriesSpecsSelector, + mergeYCustomDomainsByGroupIdSelector, + getDeselectedSeriesSelector, + getSettingsSpecSelector, + getSmallMultiplesIndexOrderSelector, + ], + (seriesSpecs, customYDomainsByGroupId, deselectedDataSeries, settingsSpec, smallMultiples): SeriesDomainsAndData => { + return computeSeriesDomains( seriesSpecs, customYDomainsByGroupId, deselectedDataSeries, @@ -41,7 +47,7 @@ export const computeSeriesDomainsSelector = createCachedSelector( settingsSpec.orderOrdinalBinsBy, // @ts-ignore blind sort option for vislib settingsSpec.enableVislibSeriesSort, + smallMultiples, ); - return domains; }, )(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/compute_series_geometries.ts b/src/chart_types/xy_chart/state/selectors/compute_series_geometries.ts index 5b63d1983e..689444acb5 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_series_geometries.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_series_geometries.ts @@ -24,8 +24,8 @@ import { getChartThemeSelector } from '../../../../state/selectors/get_chart_the import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { ComputedGeometries } from '../utils/types'; import { computeSeriesGeometries } from '../utils/utils'; -import { computeChartDimensionsSelector } from './compute_chart_dimensions'; import { computeSeriesDomainsSelector } from './compute_series_domains'; +import { computeSmallMultipleScalesSelector } from './compute_small_multiple_scales'; import { getSeriesColorsSelector } from './get_series_color_map'; import { getSeriesSpecsSelector, getAxisSpecsSelector } from './get_specs'; import { isHistogramModeEnabledSelector } from './is_histogram_mode_enabled'; @@ -38,8 +38,8 @@ export const computeSeriesGeometriesSelector = createCachedSelector( computeSeriesDomainsSelector, getSeriesColorsSelector, getChartThemeSelector, - computeChartDimensionsSelector, getAxisSpecsSelector, + computeSmallMultipleScalesSelector, isHistogramModeEnabledSelector, ], ( @@ -48,21 +48,18 @@ export const computeSeriesGeometriesSelector = createCachedSelector( seriesDomainsAndData, seriesColors, chartTheme, - chartDimensions, axesSpecs, + smallMultiplesScales, isHistogramMode, ): ComputedGeometries => { - const { xDomain, yDomain, formattedDataSeries } = seriesDomainsAndData; return computeSeriesGeometries( seriesSpecs, - xDomain, - yDomain, - formattedDataSeries, + seriesDomainsAndData, seriesColors, chartTheme, - chartDimensions.chartDimensions, settingsSpec.rotation, axesSpecs, + smallMultiplesScales, isHistogramMode, ); }, diff --git a/src/chart_types/xy_chart/state/selectors/compute_small_multiple_scales.ts b/src/chart_types/xy_chart/state/selectors/compute_small_multiple_scales.ts new file mode 100644 index 0000000000..82af6d6edd --- /dev/null +++ b/src/chart_types/xy_chart/state/selectors/compute_small_multiple_scales.ts @@ -0,0 +1,73 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import createCachedSelector from 're-reselect'; + +import { ChartTypes } from '../../..'; +import { ScaleBand } from '../../../../scales'; +import { SpecTypes } from '../../../../specs/constants'; +import { + DEFAULT_SINGLE_PANEL_SM_VALUE, + DEFAULT_SM_PANEL_PADDING, + SmallMultiplesSpec, +} from '../../../../specs/small_multiples'; +import { GlobalChartState } from '../../../../state/chart_state'; +import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { getSpecsFromStore } from '../../../../state/utils'; +import { Domain } from '../../../../utils/domain'; +import { computeChartDimensionsSelector } from './compute_chart_dimensions'; +import { computeSeriesDomainsSelector } from './compute_series_domains'; + +/** @internal */ +export interface SmallMultipleScales { + horizontal: ScaleBand; + vertical: ScaleBand; +} + +const getSmallMultipleSpec = (state: GlobalChartState) => { + const smallMultiples = getSpecsFromStore( + state.specs, + ChartTypes.Global, + SpecTypes.SmallMultiples, + ); + if (smallMultiples.length !== 1) { + return undefined; + } + return smallMultiples[0]; +}; + +/** + * Return the small multiple scales for horizontal and vertical grids + * @internal + */ +export const computeSmallMultipleScalesSelector = createCachedSelector( + [computeSeriesDomainsSelector, computeChartDimensionsSelector, getSmallMultipleSpec], + ({ smHDomain, smVDomain }, { chartDimensions: { width, height } }, smSpec): SmallMultipleScales => { + return { + horizontal: getScale(smHDomain, width, smSpec?.style?.horizontalPanelPadding), + vertical: getScale(smVDomain, height, smSpec?.style?.verticalPanelPadding), + }; + }, +)(getChartIdSelector); + +function getScale(domain: Domain, maxRange: number, padding = DEFAULT_SM_PANEL_PADDING) { + const singlePanelSmallMultiple = domain.length <= 1; + const defaultDomain = domain.length === 0 ? [DEFAULT_SINGLE_PANEL_SM_VALUE] : domain; + return new ScaleBand(defaultDomain, [0, maxRange], undefined, singlePanelSmallMultiple ? 0 : padding); +} diff --git a/src/chart_types/xy_chart/state/selectors/count_bars_in_cluster.ts b/src/chart_types/xy_chart/state/selectors/count_bars_in_cluster.ts index 903eab8027..fd8cfc809a 100644 --- a/src/chart_types/xy_chart/state/selectors/count_bars_in_cluster.ts +++ b/src/chart_types/xy_chart/state/selectors/count_bars_in_cluster.ts @@ -19,17 +19,45 @@ import createCachedSelector from 're-reselect'; +import { SeriesTypes } from '../../../../specs'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; -import { countBarsInCluster } from '../../utils/scales'; +import { groupBy } from '../../utils/group_data_series'; +import { SeriesDomainsAndData } from '../utils/types'; +import { getBarIndexKey } from '../utils/utils'; import { computeSeriesDomainsSelector } from './compute_series_domains'; +import { isHistogramModeEnabledSelector } from './is_histogram_mode_enabled'; /** @internal */ export const countBarsInClusterSelector = createCachedSelector( - [computeSeriesDomainsSelector], - (seriesDomainsAndData): number => { - const { formattedDataSeries } = seriesDomainsAndData; - - const { totalBarsInCluster } = countBarsInCluster(formattedDataSeries.stacked, formattedDataSeries.nonStacked); - return totalBarsInCluster; - }, + [computeSeriesDomainsSelector, isHistogramModeEnabledSelector], + countBarsInCluster, )(getChartIdSelector); + +/** @internal */ +export function countBarsInCluster({ formattedDataSeries }: SeriesDomainsAndData, isHistogramEnabled: boolean): number { + const barDataSeries = formattedDataSeries.filter(({ seriesType }) => seriesType === SeriesTypes.Bar); + + const dataSeriesGroupedByPanel = groupBy( + barDataSeries, + ['smVerticalAccessorValue', 'smHorizontalAccessorValue'], + false, + ); + + const barIndexByPanel = Object.keys(dataSeriesGroupedByPanel).reduce>((acc, panelKey) => { + const panelBars = dataSeriesGroupedByPanel[panelKey]; + const barDataSeriesByBarIndex = groupBy( + panelBars, + (d) => { + return getBarIndexKey(d, isHistogramEnabled); + }, + false, + ); + + acc[panelKey] = Object.keys(barDataSeriesByBarIndex); + return acc; + }, {}); + + return Object.values(barIndexByPanel).reduce((acc, curr) => { + return Math.max(acc, curr.length); + }, 0); +} diff --git a/src/chart_types/xy_chart/state/selectors/get_axis_styles.ts b/src/chart_types/xy_chart/state/selectors/get_axis_styles.ts index ec732a09a3..5f71e088b0 100644 --- a/src/chart_types/xy_chart/state/selectors/get_axis_styles.ts +++ b/src/chart_types/xy_chart/state/selectors/get_axis_styles.ts @@ -24,6 +24,7 @@ import { getChartThemeSelector } from '../../../../state/selectors/get_chart_the import { mergePartial, RecursivePartial } from '../../../../utils/commons'; import { AxisId } from '../../../../utils/ids'; import { AxisStyle } from '../../../../utils/themes/theme'; +import { isVerticalAxis } from '../../utils/axis_type_utils'; import { getAxisSpecsSelector } from './get_specs'; /** @@ -35,9 +36,16 @@ export const getAxesStylesSelector = createCachedSelector( [getAxisSpecsSelector, getChartThemeSelector], (axesSpecs, { axes: sharedAxesStyle }): Map => { const axesStyles = new Map(); - axesSpecs.forEach(({ id, style }) => { + axesSpecs.forEach(({ id, style, gridLine, position }) => { + const isVertical = isVerticalAxis(position); + const axisStyleMerge: RecursivePartial = { + ...style, + }; + if (gridLine) { + axisStyleMerge.gridLine = { [isVertical ? 'vertical' : 'horizontal']: gridLine }; + } const newStyle = style - ? mergePartial(sharedAxesStyle, style as RecursivePartial, { + ? mergePartial(sharedAxesStyle, axisStyleMerge, { mergeOptionalPartialValues: true, }) : null; diff --git a/src/chart_types/xy_chart/state/selectors/get_cursor_band.ts b/src/chart_types/xy_chart/state/selectors/get_cursor_band.ts index 2c8ac9e2ab..978eed52c7 100644 --- a/src/chart_types/xy_chart/state/selectors/get_cursor_band.ts +++ b/src/chart_types/xy_chart/state/selectors/get_cursor_band.ts @@ -21,20 +21,22 @@ import createCachedSelector from 're-reselect'; import { Scale } from '../../../../scales'; import { SettingsSpec, PointerEvent } from '../../../../specs/settings'; +import { DEFAULT_SINGLE_PANEL_SM_VALUE } from '../../../../specs/small_multiples'; import { GlobalChartState } from '../../../../state/chart_state'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { Dimensions } from '../../../../utils/dimensions'; import { isValidPointerOverEvent } from '../../../../utils/events'; -import { Point } from '../../../../utils/point'; import { getCursorBandPosition } from '../../crosshair/crosshair_utils'; import { BasicSeriesSpec } from '../../utils/specs'; import { isLineAreaOnlyChart } from '../utils/common'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; import { computeSeriesGeometriesSelector } from './compute_series_geometries'; +import { computeSmallMultipleScalesSelector, SmallMultipleScales } from './compute_small_multiple_scales'; import { countBarsInClusterSelector } from './count_bars_in_cluster'; import { getGeometriesIndexKeysSelector } from './get_geometries_index_keys'; import { getOrientedProjectedPointerPositionSelector } from './get_oriented_projected_pointer_position'; +import { PointerPosition } from './get_projected_pointer_position'; import { getSeriesSpecsSelector } from './get_specs'; import { isTooltipSnapEnableSelector } from './is_tooltip_snap_enabled'; @@ -52,6 +54,7 @@ export const getCursorBandPositionSelector = createCachedSelector( countBarsInClusterSelector, isTooltipSnapEnableSelector, getGeometriesIndexKeysSelector, + computeSmallMultipleScalesSelector, ], ( orientedProjectedPointerPosition, @@ -63,6 +66,7 @@ export const getCursorBandPositionSelector = createCachedSelector( totalBarsInCluster, isTooltipSnapEnabled, geometriesIndexKeys, + smallMultipleScales, ) => getCursorBand( orientedProjectedPointerPosition, @@ -74,11 +78,12 @@ export const getCursorBandPositionSelector = createCachedSelector( totalBarsInCluster, isTooltipSnapEnabled, geometriesIndexKeys, + smallMultipleScales, ), )(getChartIdSelector); function getCursorBand( - orientedProjectedPoinerPosition: Point, + orientedProjectedPointerPosition: PointerPosition, externalPointerEvent: PointerEvent | null, chartDimensions: Dimensions, settingsSpec: SettingsSpec, @@ -87,37 +92,54 @@ function getCursorBand( totalBarsInCluster: number, isTooltipSnapEnabled: boolean, geometriesIndexKeys: (string | number)[], + smallMultipleScales: SmallMultipleScales, ): (Dimensions & { visible: boolean; fromExternalEvent: boolean }) | undefined { - // update che cursorBandPosition based on chart configuration - const isLineAreaOnly = isLineAreaOnlyChart(seriesSpecs); if (!xScale) { return; } - let pointerPosition = orientedProjectedPoinerPosition; + // update che cursorBandPosition based on chart configuration + const isLineAreaOnly = isLineAreaOnlyChart(seriesSpecs); + + let pointerPosition = { ...orientedProjectedPointerPosition }; + let xValue; let fromExternalEvent = false; - // external pointer events takes precendence over the current mouse pointer + // external pointer events takes precedence over the current mouse pointer if (isValidPointerOverEvent(xScale, externalPointerEvent)) { fromExternalEvent = true; const x = xScale.pureScale(externalPointerEvent.value); if (x == null || x > chartDimensions.width || x < 0) { return; } - pointerPosition = { x, y: 0 }; + pointerPosition = { + x, + y: 0, + verticalPanelValue: DEFAULT_SINGLE_PANEL_SM_VALUE, + horizontalPanelValue: DEFAULT_SINGLE_PANEL_SM_VALUE, + }; xValue = { value: externalPointerEvent.value, withinBandwidth: true, }; } else { - xValue = xScale.invertWithStep(orientedProjectedPoinerPosition.x, geometriesIndexKeys); + xValue = xScale.invertWithStep(orientedProjectedPointerPosition.x, geometriesIndexKeys); if (!xValue) { return; } } + const { horizontal, vertical } = smallMultipleScales; + const topPos = vertical.scale(pointerPosition.verticalPanelValue) || 0; + const leftPos = horizontal.scale(pointerPosition.horizontalPanelValue) || 0; + const panel = { + width: horizontal.bandwidth, + height: vertical.bandwidth, + top: chartDimensions.top + topPos, + left: chartDimensions.left + leftPos, + }; const cursorBand = getCursorBandPosition( settingsSpec.rotation, - chartDimensions, + panel, pointerPosition, { value: xValue.value, diff --git a/src/chart_types/xy_chart/state/selectors/get_debug_state.ts b/src/chart_types/xy_chart/state/selectors/get_debug_state.ts index fac3519cad..05ad347158 100644 --- a/src/chart_types/xy_chart/state/selectors/get_debug_state.ts +++ b/src/chart_types/xy_chart/state/selectors/get_debug_state.ts @@ -20,6 +20,7 @@ import createCachedSelector from 're-reselect'; import { LegendItem } from '../../../../commons/legend'; +import { Line } from '../../../../geoms/types'; import { AxisSpec } from '../../../../specs'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { @@ -31,12 +32,15 @@ import { DebugStateBar, DebugStateLegend, } from '../../../../state/types'; -import { AreaGeometry, BandedAccessorType, LineGeometry, BarGeometry } from '../../../../utils/geometry'; +import { AreaGeometry, BandedAccessorType, LineGeometry, BarGeometry, PerPanel } from '../../../../utils/geometry'; import { FillStyle, Visible, StrokeStyle, Opacity } from '../../../../utils/themes/theme'; import { isVerticalAxis } from '../../utils/axis_type_utils'; -import { computeAxisVisibleTicksSelector, AxisVisibleTicks } from './compute_axis_visible_ticks'; +import { AxisGeometry } from '../../utils/axis_utils'; +import { LinesGrid } from '../../utils/grid_lines'; +import { computeAxesGeometriesSelector } from './compute_axes_geometries'; import { computeLegendSelector } from './compute_legend'; import { computeSeriesGeometriesSelector } from './compute_series_geometries'; +import { computeGridLinesSelector } from './get_grid_lines'; import { getAxisSpecsSelector } from './get_specs'; /** @@ -44,13 +48,19 @@ import { getAxisSpecsSelector } from './get_specs'; * @internal */ export const getDebugStateSelector = createCachedSelector( - [computeSeriesGeometriesSelector, computeLegendSelector, computeAxisVisibleTicksSelector, getAxisSpecsSelector], - ({ geometries }, legend, axes, axesSpecs): DebugState => { + [ + computeSeriesGeometriesSelector, + computeLegendSelector, + computeAxesGeometriesSelector, + computeGridLinesSelector, + getAxisSpecsSelector, + ], + ({ geometries }, legend, axes, gridLines, axesSpecs): DebugState => { const seriesNameMap = getSeriesNameMap(legend); return { legend: getLegendState(legend), - axes: getAxes(axes, axesSpecs), + axes: getAxes(axes, axesSpecs, gridLines), areas: geometries.areas.map(getAreaState(seriesNameMap)), lines: geometries.lines.map(getLineState(seriesNameMap)), bars: getBarsState(seriesNameMap, geometries.bars), @@ -58,18 +68,33 @@ export const getDebugStateSelector = createCachedSelector( }, )(getChartIdSelector); -const getAxes = (ticks: AxisVisibleTicks, axesSpecs: AxisSpec[]): DebugStateAxes | undefined => { +function getAxes(axesGeoms: AxisGeometry[], axesSpecs: AxisSpec[], gridLines: LinesGrid[]): DebugStateAxes | undefined { if (axesSpecs.length === 0) { return; } return axesSpecs.reduce( (acc, { position, title, id }) => { - const axisTicks = ticks.axisVisibleTicks.get(id) ?? []; - const labels = axisTicks.map(({ label }) => label); - const values = axisTicks.map(({ value }) => value); - const grids = ticks.axisGridLinesPositions.get(id) ?? []; - const gridlines = grids.map(([x, y]) => ({ x, y })); + const geom = axesGeoms.find(({ axis }) => axis.id === id); + if (!geom) { + return acc; + } + + const { ticks } = geom; + const labels = ticks.map(({ label }) => label); + const values = ticks.map(({ value }) => value); + + const gridlines = gridLines + .reduce((accLines, { lineGroups }) => { + const groupLines = lineGroups.find(({ axisId }) => { + return axisId === geom.axis.id; + }); + if (!groupLines) { + return accLines; + } + return [...accLines, ...groupLines.lines]; + }, []) + .map(({ x1, y1 }) => ({ x: x1, y: y1 })); if (isVerticalAxis(position)) { acc.y.push({ @@ -99,12 +124,17 @@ const getAxes = (ticks: AxisVisibleTicks, axesSpecs: AxisSpec[]): DebugStateAxes x: [], }, ); -}; +} -const getBarsState = (seriesNameMap: Map, barGeometries: BarGeometry[]): DebugStateBar[] => { +function getBarsState( + seriesNameMap: Map, + barGeometries: Array>, +): DebugStateBar[] { const buckets = new Map(); - - barGeometries.forEach( + const bars = barGeometries.reduce((acc, bars) => { + return [...acc, ...bars.value]; + }, []); + bars.forEach( ({ color, seriesIdentifier: { key }, @@ -136,86 +166,94 @@ const getBarsState = (seriesNameMap: Map, barGeometries: BarGeom ); return [...buckets.values()]; -}; - -const getLineState = (seriesNameMap: Map) => ({ - line: path, - points, - color, - seriesIdentifier: { key }, - seriesLineStyle, - seriesPointStyle, -}: LineGeometry): DebugStateLine => { - const name = seriesNameMap.get(key) ?? ''; - - return { - path, - color, - key, - name, - visible: hasVisibleStyle(seriesLineStyle), - visiblePoints: hasVisibleStyle(seriesPointStyle), - points: points.map(({ value: { x, y, mark } }) => ({ x, y, mark })), - }; -}; - -const getAreaState = (seriesNameMap: Map) => ({ - area: path, - lines, - points, - color, - seriesIdentifier: { key }, - seriesAreaStyle, - seriesPointStyle, - seriesAreaLineStyle, -}: AreaGeometry): DebugStateArea => { - const [y1Path, y0Path] = lines; - const linePoints = points.reduce<{ - y0: DebugStateValue[]; - y1: DebugStateValue[]; - }>( - (acc, { value: { accessor, ...value } }) => { - if (accessor === BandedAccessorType.Y0) { - acc.y0.push(value); - } else { - acc.y1.push(value); - } +} - return acc; +function getLineState(seriesNameMap: Map) { + return ({ + value: { + line: path, + points, + color, + seriesIdentifier: { key }, + seriesLineStyle, + seriesPointStyle, }, - { - y0: [], - y1: [], + }: PerPanel): DebugStateLine => { + const name = seriesNameMap.get(key) ?? ''; + + return { + path, + color, + key, + name, + visible: hasVisibleStyle(seriesLineStyle), + visiblePoints: hasVisibleStyle(seriesPointStyle), + points: points.map(({ value: { x, y, mark } }) => ({ x, y, mark })), + }; + }; +} + +function getAreaState(seriesNameMap: Map) { + return ({ + value: { + area: path, + lines, + points, + color, + seriesIdentifier: { key }, + seriesAreaStyle, + seriesPointStyle, + seriesAreaLineStyle, }, - ); - const lineVisible = hasVisibleStyle(seriesAreaLineStyle); - const visiblePoints = hasVisibleStyle(seriesPointStyle); - const name = seriesNameMap.get(key) ?? ''; - - return { - path, - color, - key, - name, - visible: hasVisibleStyle(seriesAreaStyle), - lines: { - y0: y0Path - ? { - visible: lineVisible, - path: y0Path, - points: linePoints.y0, - visiblePoints, - } - : undefined, - y1: { - visible: lineVisible, - path: y1Path, - points: linePoints.y1, - visiblePoints, + }: PerPanel): DebugStateArea => { + const [y1Path, y0Path] = lines; + const linePoints = points.reduce<{ + y0: DebugStateValue[]; + y1: DebugStateValue[]; + }>( + (acc, { value: { accessor, ...value } }) => { + if (accessor === BandedAccessorType.Y0) { + acc.y0.push(value); + } else { + acc.y1.push(value); + } + + return acc; }, - }, + { + y0: [], + y1: [], + }, + ); + const lineVisible = hasVisibleStyle(seriesAreaLineStyle); + const visiblePoints = hasVisibleStyle(seriesPointStyle); + const name = seriesNameMap.get(key) ?? ''; + + return { + path, + color, + key, + name, + visible: hasVisibleStyle(seriesAreaStyle), + lines: { + y0: y0Path + ? { + visible: lineVisible, + path: y0Path, + points: linePoints.y0, + visiblePoints, + } + : undefined, + y1: { + visible: lineVisible, + path: y1Path, + points: linePoints.y1, + visiblePoints, + }, + }, + }; }; -}; +} /** * returns series key to name mapping diff --git a/src/chart_types/xy_chart/state/selectors/get_elements_at_cursor_pos.ts b/src/chart_types/xy_chart/state/selectors/get_elements_at_cursor_pos.ts index facacd197a..4966e32f4c 100644 --- a/src/chart_types/xy_chart/state/selectors/get_elements_at_cursor_pos.ts +++ b/src/chart_types/xy_chart/state/selectors/get_elements_at_cursor_pos.ts @@ -22,10 +22,9 @@ import createCachedSelector from 're-reselect'; import { PointerEvent } from '../../../../specs'; import { GlobalChartState } from '../../../../state/chart_state'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; -import { Dimensions } from '../../../../utils/dimensions'; import { isValidPointerOverEvent } from '../../../../utils/events'; import { IndexedGeometry } from '../../../../utils/geometry'; -import { Point } from '../../../../utils/point'; +import { ChartDimensions } from '../../utils/dimensions'; import { IndexedGeometryMap } from '../../utils/indexed_geometry_map'; import { ComputedScales } from '../utils/types'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; @@ -33,6 +32,7 @@ import { getComputedScalesSelector } from './get_computed_scales'; import { getGeometriesIndexSelector } from './get_geometries_index'; import { getGeometriesIndexKeysSelector } from './get_geometries_index_keys'; import { getOrientedProjectedPointerPositionSelector } from './get_oriented_projected_pointer_position'; +import { PointerPosition } from './get_projected_pointer_position'; const getExternalPointerEventStateSelector = (state: GlobalChartState) => state.externalEvents.pointer; @@ -50,16 +50,12 @@ export const getElementAtCursorPositionSelector = createCachedSelector( )(getChartIdSelector); function getElementAtCursorPosition( - orientedProjectedPoinerPosition: Point, + orientedProjectedPointerPosition: PointerPosition, scales: ComputedScales, geometriesIndexKeys: (string | number)[], geometriesIndex: IndexedGeometryMap, externalPointerEvent: PointerEvent | null, - { - chartDimensions, - }: { - chartDimensions: Dimensions; - }, + { chartDimensions }: ChartDimensions, ): IndexedGeometry[] { if (isValidPointerOverEvent(scales.xScale, externalPointerEvent)) { const x = scales.xScale.pureScale(externalPointerEvent.value); @@ -70,10 +66,15 @@ function getElementAtCursorPosition( // TODO: Handle external event with spatial points return geometriesIndex.find(externalPointerEvent.value, { x: -1, y: -1 }); } - const xValue = scales.xScale.invertWithStep(orientedProjectedPoinerPosition.x, geometriesIndexKeys); + const xValue = scales.xScale.invertWithStep(orientedProjectedPointerPosition.x, geometriesIndexKeys); if (!xValue) { return []; } // get the elements at cursor position - return geometriesIndex.find(xValue?.value, orientedProjectedPoinerPosition); + return geometriesIndex.find( + xValue?.value, + orientedProjectedPointerPosition, + orientedProjectedPointerPosition.horizontalPanelValue, + orientedProjectedPointerPosition.verticalPanelValue, + ); } diff --git a/src/chart_types/xy_chart/state/selectors/get_grid_lines.ts b/src/chart_types/xy_chart/state/selectors/get_grid_lines.ts new file mode 100644 index 0000000000..23efeab584 --- /dev/null +++ b/src/chart_types/xy_chart/state/selectors/get_grid_lines.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import createCachedSelector from 're-reselect'; + +import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; +import { getGridLines, LinesGrid } from '../../utils/grid_lines'; +import { computeAxesGeometriesSelector } from './compute_axes_geometries'; +import { computeSmallMultipleScalesSelector } from './compute_small_multiple_scales'; +import { getAxisSpecsSelector } from './get_specs'; + +/** @internal */ +export const computeGridLinesSelector = createCachedSelector( + [getChartThemeSelector, getAxisSpecsSelector, computeAxesGeometriesSelector, computeSmallMultipleScalesSelector], + (chartTheme, axesSpecs, axesGeoms, scales): LinesGrid[] => { + return getGridLines(axesSpecs, axesGeoms, chartTheme.axes, scales); + }, +)(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/get_oriented_projected_pointer_position.ts b/src/chart_types/xy_chart/state/selectors/get_oriented_projected_pointer_position.ts index 096538757a..40cc23c896 100644 --- a/src/chart_types/xy_chart/state/selectors/get_oriented_projected_pointer_position.ts +++ b/src/chart_types/xy_chart/state/selectors/get_oriented_projected_pointer_position.ts @@ -22,30 +22,28 @@ import createCachedSelector from 're-reselect'; import { SettingsSpec } from '../../../../specs/settings'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { Dimensions } from '../../../../utils/dimensions'; -import { Point } from '../../../../utils/point'; import { getOrientedXPosition, getOrientedYPosition } from '../../utils/interactions'; -import { computeChartDimensionsSelector } from './compute_chart_dimensions'; -import { getProjectedPointerPositionSelector } from './get_projected_pointer_position'; +import { getPanelSize } from '../../utils/panel'; +import { computeSmallMultipleScalesSelector, SmallMultipleScales } from './compute_small_multiple_scales'; +import { getProjectedPointerPositionSelector, PointerPosition } from './get_projected_pointer_position'; /** @internal */ export const getOrientedProjectedPointerPositionSelector = createCachedSelector( - [getProjectedPointerPositionSelector, computeChartDimensionsSelector, getSettingsSpecSelector], + [getProjectedPointerPositionSelector, getSettingsSpecSelector, computeSmallMultipleScalesSelector], getOrientedProjectedPointerPosition, )(getChartIdSelector); function getOrientedProjectedPointerPosition( - projectedPointerPosition: Point, - chartDimensions: { chartDimensions: Dimensions }, + { x, y, horizontalPanelValue, verticalPanelValue }: PointerPosition, settingsSpec: SettingsSpec, -): Point { - const xPos = projectedPointerPosition.x; - const yPos = projectedPointerPosition.y; + scales: SmallMultipleScales, +): PointerPosition { // get the oriented projected pointer position - const x = getOrientedXPosition(xPos, yPos, settingsSpec.rotation, chartDimensions.chartDimensions); - const y = getOrientedYPosition(xPos, yPos, settingsSpec.rotation, chartDimensions.chartDimensions); + const panel = getPanelSize(scales); return { - x, - y, + x: getOrientedXPosition(x, y, settingsSpec.rotation, panel), + y: getOrientedYPosition(x, y, settingsSpec.rotation, panel), + horizontalPanelValue, + verticalPanelValue, }; } diff --git a/src/chart_types/xy_chart/state/selectors/get_projected_pointer_position.ts b/src/chart_types/xy_chart/state/selectors/get_projected_pointer_position.ts index b946d66724..e4f4e02eec 100644 --- a/src/chart_types/xy_chart/state/selectors/get_projected_pointer_position.ts +++ b/src/chart_types/xy_chart/state/selectors/get_projected_pointer_position.ts @@ -19,40 +19,84 @@ import createCachedSelector from 're-reselect'; +import { ScaleBand } from '../../../../scales/scale_band'; import { GlobalChartState } from '../../../../state/chart_state'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { Dimensions } from '../../../../utils/dimensions'; import { Point } from '../../../../utils/point'; +import { PrimitiveValue } from '../../../partition_chart/layout/utils/group_by_rollup'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; +import { computeSmallMultipleScalesSelector, SmallMultipleScales } from './compute_small_multiple_scales'; const getCurrentPointerPosition = (state: GlobalChartState) => state.interactions.pointer.current.position; -/** @internal */ +export type PointerPosition = Point & { horizontalPanelValue: PrimitiveValue; verticalPanelValue: PrimitiveValue }; +/** + * Get the x and y pointer position relative to the chart projection area + * @internal + */ export const getProjectedPointerPositionSelector = createCachedSelector( - [getCurrentPointerPosition, computeChartDimensionsSelector], - (currentPointerPosition, chartDimensions): Point => - getProjectedPointerPosition(currentPointerPosition, chartDimensions.chartDimensions), + [getCurrentPointerPosition, computeChartDimensionsSelector, computeSmallMultipleScalesSelector], + (currentPointerPosition, { chartDimensions }, smallMultipleScales): PointerPosition => + getProjectedPointerPosition(currentPointerPosition, chartDimensions, smallMultipleScales), )(getChartIdSelector); /** * Get the x and y pointer position relative to the chart projection area * @param chartAreaPointerPosition the pointer position relative to the chart area + * @param horizontal SmallMultipleScales horizontal panel scale + * @param vertical SmallMultipleScales vertical panel scale * @param chartAreaDimensions the chart dimensions */ -function getProjectedPointerPosition(chartAreaPointerPosition: Point, chartAreaDimensions: Dimensions): Point { +function getProjectedPointerPosition( + chartAreaPointerPosition: Point, + { left, top, width, height }: Dimensions, + { horizontal, vertical }: SmallMultipleScales, +): PointerPosition { const { x, y } = chartAreaPointerPosition; // get positions relative to chart - let xPos = x - chartAreaDimensions.left; - let yPos = y - chartAreaDimensions.top; + let xPos = x - left; + let yPos = y - top; + // limit cursorPosition to the chart area - if (xPos < 0 || xPos >= chartAreaDimensions.width) { + if (xPos < 0 || xPos >= width) { xPos = -1; } - if (yPos < 0 || yPos >= chartAreaDimensions.height) { + if (yPos < 0 || yPos >= height) { yPos = -1; } + const h = getPosRelativeToPanel(horizontal, xPos); + const v = getPosRelativeToPanel(vertical, yPos); + + return { + x: h.pos, + y: v.pos, + horizontalPanelValue: h.value, + verticalPanelValue: v.value, + }; +} + +function getPosRelativeToPanel(panelScale: ScaleBand, pos: number): { pos: number; value: PrimitiveValue } { + const outerPadding = panelScale.outerPadding * panelScale.step; + const innerPadding = panelScale.innerPadding * panelScale.step; + const numOfDomainSteps = panelScale.domain.length; + const rangeWithoutOuterPaddings = numOfDomainSteps * panelScale.bandwidth + (numOfDomainSteps - 1) * innerPadding; + + if (pos < outerPadding || pos > outerPadding + rangeWithoutOuterPaddings) { + return { pos: -1, value: null }; + } + const posWOInitialOuterPadding = pos - outerPadding; + const minEqualSteps = (numOfDomainSteps - 1) * panelScale.step; + if (posWOInitialOuterPadding <= minEqualSteps) { + const relativePosIndex = Math.floor(posWOInitialOuterPadding / panelScale.step); + const relativePos = posWOInitialOuterPadding - panelScale.step * relativePosIndex; + if (relativePos > panelScale.bandwidth) { + return { pos: -1, value: null }; + } + return { pos: relativePos, value: panelScale.domain[relativePosIndex] }; + } return { - x: xPos, - y: yPos, + pos: posWOInitialOuterPadding - panelScale.step * (numOfDomainSteps - 1), + value: panelScale.domain[numOfDomainSteps - 1], }; } diff --git a/src/chart_types/xy_chart/state/selectors/get_specs.ts b/src/chart_types/xy_chart/state/selectors/get_specs.ts index 23e5c9cf62..8b3203dddc 100644 --- a/src/chart_types/xy_chart/state/selectors/get_specs.ts +++ b/src/chart_types/xy_chart/state/selectors/get_specs.ts @@ -20,6 +20,7 @@ import createCachedSelector from 're-reselect'; import { ChartTypes } from '../../..'; +import { GroupBySpec, SmallMultiplesSpec } from '../../../../specs'; import { SpecTypes } from '../../../../specs/constants'; import { GlobalChartState } from '../../../../state/chart_state'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; @@ -35,11 +36,35 @@ export const getAxisSpecsSelector = createCachedSelector([getSpecs], (specs): Ax /** @internal */ export const getSeriesSpecsSelector = createCachedSelector([getSpecs], (specs) => { - const seriesSpec = getSpecsFromStore(specs, ChartTypes.XYAxis, SpecTypes.Series); - return seriesSpec; + return getSpecsFromStore(specs, ChartTypes.XYAxis, SpecTypes.Series); })(getChartIdSelector); /** @internal */ export const getAnnotationSpecsSelector = createCachedSelector([getSpecs], (specs) => getSpecsFromStore(specs, ChartTypes.XYAxis, SpecTypes.Annotation), )(getChartIdSelector); + +/** @internal */ +export const getSmallMultiplesIndexOrderSelector = createCachedSelector([getSpecs], (specs) => { + const smallMultiples = getSpecsFromStore(specs, ChartTypes.Global, SpecTypes.SmallMultiples); + if (smallMultiples.length !== 1) { + return undefined; + } + const indexOrders = getSpecsFromStore(specs, ChartTypes.Global, SpecTypes.IndexOrder); + const [smallMultiplesConfig] = smallMultiples; + + let vertical: GroupBySpec | undefined; + let horizontal: GroupBySpec | undefined; + + if (smallMultiplesConfig.splitVertically) { + vertical = indexOrders.find((d) => d.id === smallMultiplesConfig.splitVertically); + } + if (smallMultiplesConfig.splitHorizontally) { + horizontal = indexOrders.find((d) => d.id === smallMultiplesConfig.splitHorizontally); + } + + return { + vertical, + horizontal, + }; +})(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts index bc58216492..97b6f6faad 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts +++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts @@ -21,10 +21,10 @@ import createCachedSelector from 're-reselect'; import { TooltipAnchorPosition } from '../../../../components/tooltip/types'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; -import { getLegendSizeSelector } from '../../../../state/selectors/get_legend_size'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { getTooltipAnchorPosition } from '../../crosshair/crosshair_utils'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; +import { computeSmallMultipleScalesSelector } from './compute_small_multiple_scales'; import { getCursorBandPositionSelector } from './get_cursor_band'; import { getProjectedPointerPositionSelector } from './get_projected_pointer_position'; @@ -35,13 +35,35 @@ export const getTooltipAnchorPositionSelector = createCachedSelector( getSettingsSpecSelector, getCursorBandPositionSelector, getProjectedPointerPositionSelector, - getLegendSizeSelector, + computeSmallMultipleScalesSelector, ], - (chartDimensions, settings, cursorBandPosition, projectedPointerPosition): TooltipAnchorPosition | null => { + ( + chartDimensions, + settings, + cursorBandPosition, + projectedPointerPosition, + { horizontal, vertical }, + ): TooltipAnchorPosition | null => { if (!cursorBandPosition) { return null; } - return getTooltipAnchorPosition(chartDimensions, settings.rotation, cursorBandPosition, projectedPointerPosition); + const topPos = vertical.scale(projectedPointerPosition.verticalPanelValue) || 0; + const leftPos = horizontal.scale(projectedPointerPosition.horizontalPanelValue) || 0; + + const panel = { + width: horizontal.bandwidth, + height: vertical.bandwidth, + top: chartDimensions.chartDimensions.top + topPos, + left: chartDimensions.chartDimensions.left + leftPos, + }; + + return getTooltipAnchorPosition( + chartDimensions, + settings.rotation, + cursorBandPosition, + projectedPointerPosition, + panel, + ); }, )(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts index 240dc18fbd..734eae4edb 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts +++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts @@ -199,7 +199,8 @@ function getTooltipAndHighlightFromValue( return { tooltip: { header, - values, + // to avoid creating a breaking change because of a different sorting order on tooltip + values: values.reverse(), }, highlightedGeometries, }; diff --git a/src/chart_types/xy_chart/state/selectors/on_pointer_move_caller.ts b/src/chart_types/xy_chart/state/selectors/on_pointer_move_caller.ts index ba3c8728f8..1514dea375 100644 --- a/src/chart_types/xy_chart/state/selectors/on_pointer_move_caller.ts +++ b/src/chart_types/xy_chart/state/selectors/on_pointer_move_caller.ts @@ -27,10 +27,10 @@ import { PointerEventType } from '../../../../specs/constants'; import { GlobalChartState } from '../../../../state/chart_state'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { Point } from '../../../../utils/point'; import { computeSeriesGeometriesSelector } from './compute_series_geometries'; import { getGeometriesIndexKeysSelector } from './get_geometries_index_keys'; import { getOrientedProjectedPointerPositionSelector } from './get_oriented_projected_pointer_position'; +import { PointerPosition } from './get_projected_pointer_position'; const getPointerEventSelector = createCachedSelector( [ @@ -45,7 +45,7 @@ const getPointerEventSelector = createCachedSelector( function getPointerEvent( chartId: string, - orientedProjectedPoinerPosition: Point, + orientedProjectedPointerPosition: PointerPosition, xScale: Scale | undefined, geometriesIndexKeys: any[], ): PointerEvent { @@ -56,7 +56,7 @@ function getPointerEvent( type: PointerEventType.Out, }; } - const { x, y } = orientedProjectedPoinerPosition; + const { x, y } = orientedProjectedPointerPosition; if (x === -1 || y === -1) { return { chartId, @@ -122,7 +122,7 @@ export function createOnPointerMoveCaller(): (state: GlobalChartState) => void { const tempPrev = { ...prevPointerEvent, }; - // we have to update the prevPointerEvents before possiibly calling the onPointerUpdate + // we have to update the prevPointerEvents before possibly calling the onPointerUpdate // to avoid a recursive loop of calls caused by the impossibility to update the prevPointerEvent prevPointerEvent = nextPointerEvent; if (settings && settings.onPointerUpdate && hasPointerEventChanged(tempPrev, nextPointerEvent)) { diff --git a/src/chart_types/xy_chart/state/utils/__snapshots__/utils.test.ts.snap b/src/chart_types/xy_chart/state/utils/__snapshots__/utils.test.ts.snap index da1db14e80..ad27ac9981 100644 --- a/src/chart_types/xy_chart/state/utils/__snapshots__/utils.test.ts.snap +++ b/src/chart_types/xy_chart/state/utils/__snapshots__/utils.test.ts.snap @@ -3,144 +3,216 @@ exports[`Chart State utils should compute and format specifications for non stacked chart 1`] = ` Array [ Object { - "counts": Object { - "area": 0, - "bar": 0, - "bubble": 0, - "line": 1, - }, - "dataSeries": Array [ + "data": Array [ Object { - "data": Array [ - Object { - "datum": Object { - "x": 0, - "y": 1, - }, - "initialY0": null, - "initialY1": 1, - "mark": null, - "x": 0, - "y0": null, - "y1": 1, - }, - Object { - "datum": Object { - "x": 1, - "y": 2, - }, - "initialY0": null, - "initialY1": 2, - "mark": null, - "x": 1, - "y0": null, - "y1": 2, - }, - Object { - "datum": Object { - "x": 2, - "y": 10, - }, - "initialY0": null, - "initialY1": 10, - "mark": null, - "x": 2, - "y0": null, - "y1": 10, - }, - Object { - "datum": Object { - "x": 3, - "y": 6, - }, - "initialY0": null, - "initialY1": 6, - "mark": null, - "x": 3, - "y0": null, - "y1": 6, - }, - ], - "key": "spec{spec1}yAccessor{y}splitAccessors{}", - "seriesKeys": Array [ - "y", - ], - "specId": "spec1", - "splitAccessors": Map {}, - "yAccessor": "y", + "datum": Object { + "x": 0, + "y": 1, + }, + "initialY0": null, + "initialY1": 1, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 0, + "y0": null, + "y1": 1, + }, + Object { + "datum": Object { + "x": 1, + "y": 2, + }, + "initialY0": null, + "initialY1": 2, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 1, + "y0": null, + "y1": 2, + }, + Object { + "datum": Object { + "x": 2, + "y": 10, + }, + "initialY0": null, + "initialY1": 10, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 2, + "y0": null, + "y1": 10, + }, + Object { + "datum": Object { + "x": 3, + "y": 6, + }, + "initialY0": null, + "initialY1": 6, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 3, + "y0": null, + "y1": 6, }, ], "groupId": "group1", + "isStacked": false, + "key": "groupId{group1}spec{spec1}yAccessor{y}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", + "seriesKeys": Array [ + "y", + ], + "seriesType": "line", + "smHorizontalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smVerticalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "spec": Object { + "chartType": "xy_axis", + "data": Array [ + Object { + "x": 0, + "y": 1, + }, + Object { + "x": 1, + "y": 2, + }, + Object { + "x": 2, + "y": 10, + }, + Object { + "x": 3, + "y": 6, + }, + ], + "groupId": "group1", + "hideInLegend": false, + "histogramModeAlignment": "center", + "id": "spec1", + "seriesType": "line", + "specType": "series", + "xAccessor": "x", + "xScaleType": "linear", + "yAccessors": Array [ + "y", + ], + "yScaleType": "log", + }, + "specId": "spec1", + "splitAccessors": Map {}, + "stackMode": undefined, + "yAccessor": "y", }, Object { - "counts": Object { - "area": 0, - "bar": 0, - "bubble": 0, - "line": 1, - }, - "dataSeries": Array [ + "data": Array [ + Object { + "datum": Object { + "x": 0, + "y": 1, + }, + "initialY0": null, + "initialY1": 1, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 0, + "y0": null, + "y1": 1, + }, + Object { + "datum": Object { + "x": 1, + "y": 2, + }, + "initialY0": null, + "initialY1": 2, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 1, + "y0": null, + "y1": 2, + }, Object { - "data": Array [ - Object { - "datum": Object { - "x": 0, - "y": 1, - }, - "initialY0": null, - "initialY1": 1, - "mark": null, - "x": 0, - "y0": null, - "y1": 1, - }, - Object { - "datum": Object { - "x": 1, - "y": 2, - }, - "initialY0": null, - "initialY1": 2, - "mark": null, - "x": 1, - "y0": null, - "y1": 2, - }, - Object { - "datum": Object { - "x": 2, - "y": 10, - }, - "initialY0": null, - "initialY1": 10, - "mark": null, - "x": 2, - "y0": null, - "y1": 10, - }, - Object { - "datum": Object { - "x": 3, - "y": 6, - }, - "initialY0": null, - "initialY1": 6, - "mark": null, - "x": 3, - "y0": null, - "y1": 6, - }, - ], - "key": "spec{spec2}yAccessor{y}splitAccessors{}", - "seriesKeys": Array [ - "y", - ], - "specId": "spec2", - "splitAccessors": Map {}, - "yAccessor": "y", + "datum": Object { + "x": 2, + "y": 10, + }, + "initialY0": null, + "initialY1": 10, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 2, + "y0": null, + "y1": 10, + }, + Object { + "datum": Object { + "x": 3, + "y": 6, + }, + "initialY0": null, + "initialY1": 6, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 3, + "y0": null, + "y1": 6, }, ], "groupId": "group2", + "isStacked": false, + "key": "groupId{group2}spec{spec2}yAccessor{y}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", + "seriesKeys": Array [ + "y", + ], + "seriesType": "line", + "smHorizontalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smVerticalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "spec": Object { + "chartType": "xy_axis", + "data": Array [ + Object { + "x": 0, + "y": 1, + }, + Object { + "x": 1, + "y": 2, + }, + Object { + "x": 2, + "y": 10, + }, + Object { + "x": 3, + "y": 6, + }, + ], + "groupId": "group2", + "hideInLegend": false, + "histogramModeAlignment": "center", + "id": "spec2", + "seriesType": "line", + "specType": "series", + "xAccessor": "x", + "xScaleType": "linear", + "yAccessors": Array [ + "y", + ], + "yScaleType": "log", + }, + "specId": "spec2", + "splitAccessors": Map {}, + "stackMode": undefined, + "yAccessor": "y", }, ] `; @@ -148,156 +220,282 @@ Array [ exports[`Chart State utils should compute and format specifications for stacked chart 1`] = ` Array [ Object { - "counts": Object { - "area": 0, - "bar": 0, - "bubble": 0, - "line": 2, + "data": Array [ + Object { + "datum": Object { + "g": "a", + "x": 0, + "y": 1, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 1, + "mark": null, + "x": 0, + "y0": 0, + "y1": 1, + }, + Object { + "datum": Object { + "g": "a", + "x": 1, + "y": 2, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 2, + "mark": null, + "x": 1, + "y0": 0, + "y1": 2, + }, + Object { + "datum": Object { + "g": "a", + "x": 2, + "y": 3, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 3, + "mark": null, + "x": 2, + "y0": 0, + "y1": 3, + }, + Object { + "datum": Object { + "g": "a", + "x": 3, + "y": 4, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 4, + "mark": null, + "x": 3, + "y0": 0, + "y1": 4, + }, + ], + "groupId": "group2", + "isStacked": true, + "key": "groupId{group2}spec{spec2}yAccessor{y}splitAccessors{g-a}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", + "seriesKeys": Array [ + "a", + "y", + ], + "seriesType": "line", + "smHorizontalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smVerticalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "spec": Object { + "chartType": "xy_axis", + "data": Array [ + Object { + "g": "a", + "x": 0, + "y": 1, + }, + Object { + "g": "b", + "x": 0, + "y": 2, + }, + Object { + "g": "a", + "x": 1, + "y": 2, + }, + Object { + "g": "b", + "x": 1, + "y": 3, + }, + Object { + "g": "a", + "x": 2, + "y": 3, + }, + Object { + "g": "b", + "x": 2, + "y": 4, + }, + Object { + "g": "a", + "x": 3, + "y": 4, + }, + Object { + "g": "b", + "x": 3, + "y": 5, + }, + ], + "groupId": "group2", + "hideInLegend": false, + "histogramModeAlignment": "center", + "id": "spec2", + "seriesType": "line", + "specType": "series", + "splitSeriesAccessors": Array [ + "g", + ], + "stackAccessors": Array [ + "x", + ], + "xAccessor": "x", + "xScaleType": "linear", + "yAccessors": Array [ + "y", + ], + "yScaleType": "log", }, - "dataSeries": Array [ + "specId": "spec2", + "splitAccessors": Map { + "g" => "a", + }, + "stackMode": undefined, + "yAccessor": "y", + }, + Object { + "data": Array [ Object { - "data": Array [ - Object { - "datum": Object { - "g": "a", - "x": 0, - "y": 1, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 1, - "mark": null, - "x": 0, - "y0": 0, - "y1": 1, - }, - Object { - "datum": Object { - "g": "a", - "x": 1, - "y": 2, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 2, - "mark": null, - "x": 1, - "y0": 0, - "y1": 2, - }, - Object { - "datum": Object { - "g": "a", - "x": 2, - "y": 3, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 3, - "mark": null, - "x": 2, - "y0": 0, - "y1": 3, - }, - Object { - "datum": Object { - "g": "a", - "x": 3, - "y": 4, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 4, - "mark": null, - "x": 3, - "y0": 0, - "y1": 4, - }, - ], - "key": "spec{spec2}yAccessor{y}splitAccessors{g-a}", - "seriesKeys": Array [ - "a", - "y", - ], - "specId": "spec2", - "splitAccessors": Map { - "g" => "a", - }, - "yAccessor": "y", + "datum": Object { + "g": "b", + "x": 0, + "y": 2, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 2, + "mark": null, + "x": 0, + "y0": 1, + "y1": 3, }, Object { - "data": Array [ - Object { - "datum": Object { - "g": "b", - "x": 0, - "y": 2, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 2, - "mark": null, - "x": 0, - "y0": 1, - "y1": 3, - }, - Object { - "datum": Object { - "g": "b", - "x": 1, - "y": 3, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 3, - "mark": null, - "x": 1, - "y0": 2, - "y1": 5, - }, - Object { - "datum": Object { - "g": "b", - "x": 2, - "y": 4, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 4, - "mark": null, - "x": 2, - "y0": 3, - "y1": 7, - }, - Object { - "datum": Object { - "g": "b", - "x": 3, - "y": 5, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 5, - "mark": null, - "x": 3, - "y0": 4, - "y1": 9, - }, - ], - "key": "spec{spec2}yAccessor{y}splitAccessors{g-b}", - "seriesKeys": Array [ - "b", - "y", - ], - "specId": "spec2", - "splitAccessors": Map { - "g" => "b", - }, - "yAccessor": "y", + "datum": Object { + "g": "b", + "x": 1, + "y": 3, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 3, + "mark": null, + "x": 1, + "y0": 2, + "y1": 5, + }, + Object { + "datum": Object { + "g": "b", + "x": 2, + "y": 4, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 4, + "mark": null, + "x": 2, + "y0": 3, + "y1": 7, + }, + Object { + "datum": Object { + "g": "b", + "x": 3, + "y": 5, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 5, + "mark": null, + "x": 3, + "y0": 4, + "y1": 9, }, ], "groupId": "group2", + "isStacked": true, + "key": "groupId{group2}spec{spec2}yAccessor{y}splitAccessors{g-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", + "seriesKeys": Array [ + "b", + "y", + ], + "seriesType": "line", + "smHorizontalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smVerticalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "spec": Object { + "chartType": "xy_axis", + "data": Array [ + Object { + "g": "a", + "x": 0, + "y": 1, + }, + Object { + "g": "b", + "x": 0, + "y": 2, + }, + Object { + "g": "a", + "x": 1, + "y": 2, + }, + Object { + "g": "b", + "x": 1, + "y": 3, + }, + Object { + "g": "a", + "x": 2, + "y": 3, + }, + Object { + "g": "b", + "x": 2, + "y": 4, + }, + Object { + "g": "a", + "x": 3, + "y": 4, + }, + Object { + "g": "b", + "x": 3, + "y": 5, + }, + ], + "groupId": "group2", + "hideInLegend": false, + "histogramModeAlignment": "center", + "id": "spec2", + "seriesType": "line", + "specType": "series", + "splitSeriesAccessors": Array [ + "g", + ], + "stackAccessors": Array [ + "x", + ], + "xAccessor": "x", + "xScaleType": "linear", + "yAccessors": Array [ + "y", + ], + "yScaleType": "log", + }, + "specId": "spec2", + "splitAccessors": Map { + "g" => "b", + }, "stackMode": undefined, + "yAccessor": "y", }, ] `; @@ -305,147 +503,284 @@ Array [ exports[`Chart State utils should compute and format specifications for stacked chart 2`] = ` Array [ Object { - "counts": Object { - "area": 0, - "bar": 0, - "bubble": 0, - "line": 2, + "data": Array [ + Object { + "datum": Object { + "g": "a", + "x": 0, + "y": 1, + }, + "initialY0": null, + "initialY1": 1, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 0, + "y0": null, + "y1": 1, + }, + Object { + "datum": Object { + "g": "a", + "x": 1, + "y": 2, + }, + "initialY0": null, + "initialY1": 2, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 1, + "y0": null, + "y1": 2, + }, + Object { + "datum": Object { + "g": "a", + "x": 2, + "y": 3, + }, + "initialY0": null, + "initialY1": 3, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 2, + "y0": null, + "y1": 3, + }, + Object { + "datum": Object { + "g": "a", + "x": 3, + "y": 4, + }, + "initialY0": null, + "initialY1": 4, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 3, + "y0": null, + "y1": 4, + }, + ], + "groupId": "group1", + "isStacked": false, + "key": "groupId{group1}spec{spec1}yAccessor{y}splitAccessors{g-a}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", + "seriesKeys": Array [ + "a", + "y", + ], + "seriesType": "line", + "smHorizontalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smVerticalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "spec": Object { + "chartType": "xy_axis", + "data": Array [ + Object { + "g": "a", + "x": 0, + "y": 1, + }, + Object { + "g": "b", + "x": 0, + "y": 2, + }, + Object { + "g": "a", + "x": 1, + "y": 2, + }, + Object { + "g": "b", + "x": 1, + "y": 3, + }, + Object { + "g": "a", + "x": 2, + "y": 3, + }, + Object { + "g": "b", + "x": 2, + "y": 4, + }, + Object { + "g": "a", + "x": 3, + "y": 4, + }, + Object { + "g": "b", + "x": 3, + "y": 5, + }, + ], + "groupId": "group1", + "hideInLegend": false, + "histogramModeAlignment": "center", + "id": "spec1", + "seriesType": "line", + "specType": "series", + "splitSeriesAccessors": Array [ + "g", + ], + "xAccessor": "x", + "xScaleType": "linear", + "yAccessors": Array [ + "y", + ], + "yScaleType": "log", }, - "dataSeries": Array [ + "specId": "spec1", + "splitAccessors": Map { + "g" => "a", + }, + "stackMode": undefined, + "yAccessor": "y", + }, + Object { + "data": Array [ + Object { + "datum": Object { + "g": "b", + "x": 0, + "y": 2, + }, + "initialY0": null, + "initialY1": 2, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 0, + "y0": null, + "y1": 2, + }, + Object { + "datum": Object { + "g": "b", + "x": 1, + "y": 3, + }, + "initialY0": null, + "initialY1": 3, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 1, + "y0": null, + "y1": 3, + }, Object { - "data": Array [ - Object { - "datum": Object { - "g": "a", - "x": 0, - "y": 1, - }, - "initialY0": null, - "initialY1": 1, - "mark": null, - "x": 0, - "y0": null, - "y1": 1, - }, - Object { - "datum": Object { - "g": "a", - "x": 1, - "y": 2, - }, - "initialY0": null, - "initialY1": 2, - "mark": null, - "x": 1, - "y0": null, - "y1": 2, - }, - Object { - "datum": Object { - "g": "a", - "x": 2, - "y": 3, - }, - "initialY0": null, - "initialY1": 3, - "mark": null, - "x": 2, - "y0": null, - "y1": 3, - }, - Object { - "datum": Object { - "g": "a", - "x": 3, - "y": 4, - }, - "initialY0": null, - "initialY1": 4, - "mark": null, - "x": 3, - "y0": null, - "y1": 4, - }, - ], - "key": "spec{spec1}yAccessor{y}splitAccessors{g-a}", - "seriesKeys": Array [ - "a", - "y", - ], - "specId": "spec1", - "splitAccessors": Map { - "g" => "a", - }, - "yAccessor": "y", + "datum": Object { + "g": "b", + "x": 2, + "y": 4, + }, + "initialY0": null, + "initialY1": 4, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 2, + "y0": null, + "y1": 4, }, Object { - "data": Array [ - Object { - "datum": Object { - "g": "b", - "x": 0, - "y": 2, - }, - "initialY0": null, - "initialY1": 2, - "mark": null, - "x": 0, - "y0": null, - "y1": 2, - }, - Object { - "datum": Object { - "g": "b", - "x": 1, - "y": 3, - }, - "initialY0": null, - "initialY1": 3, - "mark": null, - "x": 1, - "y0": null, - "y1": 3, - }, - Object { - "datum": Object { - "g": "b", - "x": 2, - "y": 4, - }, - "initialY0": null, - "initialY1": 4, - "mark": null, - "x": 2, - "y0": null, - "y1": 4, - }, - Object { - "datum": Object { - "g": "b", - "x": 3, - "y": 5, - }, - "initialY0": null, - "initialY1": 5, - "mark": null, - "x": 3, - "y0": null, - "y1": 5, - }, - ], - "key": "spec{spec1}yAccessor{y}splitAccessors{g-b}", - "seriesKeys": Array [ - "b", - "y", - ], - "specId": "spec1", - "splitAccessors": Map { - "g" => "b", - }, - "yAccessor": "y", + "datum": Object { + "g": "b", + "x": 3, + "y": 5, + }, + "initialY0": null, + "initialY1": 5, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 3, + "y0": null, + "y1": 5, }, ], "groupId": "group1", + "isStacked": false, + "key": "groupId{group1}spec{spec1}yAccessor{y}splitAccessors{g-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", + "seriesKeys": Array [ + "b", + "y", + ], + "seriesType": "line", + "smHorizontalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smVerticalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "spec": Object { + "chartType": "xy_axis", + "data": Array [ + Object { + "g": "a", + "x": 0, + "y": 1, + }, + Object { + "g": "b", + "x": 0, + "y": 2, + }, + Object { + "g": "a", + "x": 1, + "y": 2, + }, + Object { + "g": "b", + "x": 1, + "y": 3, + }, + Object { + "g": "a", + "x": 2, + "y": 3, + }, + Object { + "g": "b", + "x": 2, + "y": 4, + }, + Object { + "g": "a", + "x": 3, + "y": 4, + }, + Object { + "g": "b", + "x": 3, + "y": 5, + }, + ], + "groupId": "group1", + "hideInLegend": false, + "histogramModeAlignment": "center", + "id": "spec1", + "seriesType": "line", + "specType": "series", + "splitSeriesAccessors": Array [ + "g", + ], + "xAccessor": "x", + "xScaleType": "linear", + "yAccessors": Array [ + "y", + ], + "yScaleType": "log", + }, + "specId": "spec1", + "splitAccessors": Map { + "g" => "b", + }, + "stackMode": undefined, + "yAccessor": "y", }, ] `; diff --git a/src/chart_types/xy_chart/state/utils/common.ts b/src/chart_types/xy_chart/state/utils/common.ts index aadc5184ee..492982b43d 100644 --- a/src/chart_types/xy_chart/state/utils/common.ts +++ b/src/chart_types/xy_chart/state/utils/common.ts @@ -21,7 +21,9 @@ import { LegendItem } from '../../../../commons/legend'; import { Rotation } from '../../../../utils/commons'; import { BasicSeriesSpec, SeriesTypes } from '../../utils/specs'; import { GeometriesCounts } from './types'; -import { MAX_ANIMATABLE_BARS, MAX_ANIMATABLE_LINES_AREA_POINTS } from './utils'; + +export const MAX_ANIMATABLE_BARS = 300; +export const MAX_ANIMATABLE_LINES_AREA_POINTS = 600; /** @internal */ export function isHorizontalRotation(chartRotation: Rotation) { diff --git a/src/chart_types/xy_chart/state/utils/types.ts b/src/chart_types/xy_chart/state/utils/types.ts index 27352a0ac9..c524e0f4b9 100644 --- a/src/chart_types/xy_chart/state/utils/types.ts +++ b/src/chart_types/xy_chart/state/utils/types.ts @@ -18,11 +18,19 @@ */ import { SeriesKey } from '../../../../commons/series_id'; import { Scale } from '../../../../scales'; -import { PointGeometry, BarGeometry, AreaGeometry, LineGeometry, BubbleGeometry } from '../../../../utils/geometry'; +import { Domain } from '../../../../utils/domain'; +import { + PointGeometry, + BarGeometry, + AreaGeometry, + LineGeometry, + BubbleGeometry, + PerPanel, +} from '../../../../utils/geometry'; import { GroupId } from '../../../../utils/ids'; import { XDomain, YDomain } from '../../domains/types'; import { IndexedGeometryMap } from '../../utils/indexed_geometry_map'; -import { SeriesCollectionValue, FormattedDataSeries } from '../../utils/series'; +import { SeriesCollectionValue, DataSeries } from '../../utils/series'; /** @internal */ export interface Transform { @@ -52,10 +60,10 @@ export interface ComputedScales { /** @internal */ export interface Geometries { points: PointGeometry[]; - bars: BarGeometry[]; - areas: AreaGeometry[]; - lines: LineGeometry[]; - bubbles: BubbleGeometry[]; + bars: Array>; + areas: Array>; + lines: Array>; + bubbles: Array>; } /** @internal */ @@ -70,10 +78,9 @@ export interface ComputedGeometries { export interface SeriesDomainsAndData { xDomain: XDomain; yDomain: YDomain[]; - formattedDataSeries: { - stacked: FormattedDataSeries[]; - nonStacked: FormattedDataSeries[]; - }; + smVDomain: Domain; + smHDomain: Domain; + formattedDataSeries: DataSeries[]; seriesCollection: Map; } diff --git a/src/chart_types/xy_chart/state/utils/utils.test.ts b/src/chart_types/xy_chart/state/utils/utils.test.ts index 3002ea1421..12ae7a9b87 100644 --- a/src/chart_types/xy_chart/state/utils/utils.test.ts +++ b/src/chart_types/xy_chart/state/utils/utils.test.ts @@ -17,33 +17,20 @@ * under the License. */ -import { ChartTypes } from '../../..'; import { MockSeriesCollection } from '../../../../mocks/series/series_identifiers'; -import { MockSeriesSpecs, MockSeriesSpec } from '../../../../mocks/specs'; +import { MockSeriesSpecs, MockSeriesSpec, MockGlobalSpec } from '../../../../mocks/specs'; +import { MockStore } from '../../../../mocks/store'; import { SeededDataGenerator } from '../../../../mocks/utils'; import { ScaleContinuous } from '../../../../scales'; import { ScaleType } from '../../../../scales/constants'; -import { SpecTypes } from '../../../../specs/constants'; -import { ColorOverrides } from '../../../../state/chart_state'; +import { Spec } from '../../../../specs'; import { BARCHART_1Y0G, BARCHART_1Y1G } from '../../../../utils/data_samples/test_dataset'; -import { IndexedGeometry, BandedAccessorType } from '../../../../utils/geometry'; import { SpecId } from '../../../../utils/ids'; -import { LIGHT_THEME } from '../../../../utils/themes/light_theme'; -import { SeriesCollectionValue, getSeriesIndex, getSeriesColors } from '../../utils/series'; -import { - AreaSeriesSpec, - AxisSpec, - BarSeriesSpec, - BasicSeriesSpec, - HistogramModeAlignments, - LineSeriesSpec, - SeriesTypes, - SeriesColorAccessorFn, -} from '../../utils/specs'; -import { mergeYCustomDomainsByGroupId } from '../selectors/merge_y_custom_domains'; +import { SeriesCollectionValue, getSeriesIndex } from '../../utils/series'; +import { BasicSeriesSpec, HistogramModeAlignments, SeriesColorAccessorFn } from '../../utils/specs'; +import { computeSeriesGeometriesSelector } from '../selectors/compute_series_geometries'; import { computeSeriesDomains, - computeSeriesGeometries, computeXScaleOffset, isHistogramModeEnabled, setBarSeriesAccessors, @@ -51,37 +38,40 @@ import { updateDeselectedDataSeries, } from './utils'; -describe('Chart State utils', () => { - const emptySeriesOverrides: ColorOverrides = { - temporary: {}, - persisted: {}, - }; +function getGeometriesFromSpecs(specs: Spec[]) { + const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); + const settings = MockGlobalSpec.settingsNoMargins({ + theme: { + colors: { + vizColors: ['violet', 'green', 'blue'], + defaultVizColor: 'red', + }, + }, + }); + MockStore.addSpecs([...specs, settings], store); + return computeSeriesGeometriesSelector(store.getState()); +} +describe('Chart State utils', () => { it('should compute and format specifications for non stacked chart', () => { - const spec1: BasicSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const spec1 = MockSeriesSpec.line({ id: 'spec1', groupId: 'group1', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], data: BARCHART_1Y0G, - }; - const spec2: BasicSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const spec2 = MockSeriesSpec.line({ id: 'spec2', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], data: BARCHART_1Y0G, - }; + }); const domains = computeSeriesDomains([spec1, spec2], new Map()); expect(domains.xDomain).toEqual({ domain: [0, 3], @@ -106,29 +96,22 @@ describe('Chart State utils', () => { type: 'yDomain', }, ]); - expect(domains.formattedDataSeries.stacked).toEqual([]); - expect(domains.formattedDataSeries.nonStacked).toMatchSnapshot(); + expect(domains.formattedDataSeries).toMatchSnapshot(); }); it('should compute and format specifications for stacked chart', () => { - const spec1: BasicSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const spec1 = MockSeriesSpec.line({ id: 'spec1', groupId: 'group1', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const spec2: BasicSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const spec2 = MockSeriesSpec.line({ id: 'spec2', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', @@ -136,7 +119,7 @@ describe('Chart State utils', () => { splitSeriesAccessors: ['g'], stackAccessors: ['x'], data: BARCHART_1Y1G, - }; + }); const domains = computeSeriesDomains([spec1, spec2], new Map()); expect(domains.xDomain).toEqual({ domain: [0, 3], @@ -147,22 +130,22 @@ describe('Chart State utils', () => { }); expect(domains.yDomain).toEqual([ { - domain: [0, 5], + domain: [0, 9], scaleType: ScaleType.Log, - groupId: 'group1', + groupId: 'group2', isBandScale: false, type: 'yDomain', }, { - domain: [0, 9], + domain: [0, 5], scaleType: ScaleType.Log, - groupId: 'group2', + groupId: 'group1', isBandScale: false, type: 'yDomain', }, ]); - expect(domains.formattedDataSeries.stacked).toMatchSnapshot(); - expect(domains.formattedDataSeries.nonStacked).toMatchSnapshot(); + expect(domains.formattedDataSeries.filter(({ isStacked }) => isStacked)).toMatchSnapshot(); + expect(domains.formattedDataSeries.filter(({ isStacked }) => !isStacked)).toMatchSnapshot(); }); it('should check if a SeriesCollectionValue item exists in a list of SeriesCollectionValue', () => { const dataSeriesValuesA: SeriesCollectionValue = { @@ -248,7 +231,8 @@ describe('Chart State utils', () => { const dg = new SeededDataGenerator(); // 4 groups generated const data = dg.generateGroupedSeries(50, 4); - const targetKey = 'spec{bar1}yAccessor{y}splitAccessors{g-b}'; + const targetKey = + 'groupId{__global__}spec{bar1}yAccessor{y}splitAccessors{g-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}'; describe('empty series collection and specs', () => { it('it should return an empty map', () => { @@ -328,7 +312,6 @@ describe('Chart State utils', () => { it('it should return color from color function', () => { const actual = getCustomSeriesColors(barSeriesSpecs, barSeriesCollection); - expect(actual.size).toBe(1); expect(actual.get(targetKey)).toBe('aquamarine'); }); @@ -338,25 +321,19 @@ describe('Chart State utils', () => { describe('Geometries counts', () => { test('can compute stacked geometries counts', () => { - const area: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const area = MockSeriesSpec.area({ id: 'area', groupId: 'group1', - seriesType: SeriesTypes.Area, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const line: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const line = MockSeriesSpec.line({ id: 'line', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', @@ -364,13 +341,10 @@ describe('Chart State utils', () => { splitSeriesAccessors: ['g'], stackAccessors: ['x'], data: BARCHART_1Y1G, - }; - const bar: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const bar = MockSeriesSpec.bar({ id: 'bar', groupId: 'group2', - seriesType: SeriesTypes.Bar, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', @@ -378,197 +352,99 @@ describe('Chart State utils', () => { splitSeriesAccessors: ['g'], stackAccessors: ['x'], data: BARCHART_1Y1G, - }; - const seriesSpecs: BasicSeriesSpec[] = [area, line, bar]; - const axesSpecs: AxisSpec[] = []; - const chartRotation = 0; - const chartDimensions = { width: 100, height: 100, top: 0, left: 0 }; - const chartColors = { - vizColors: ['violet', 'green', 'blue'], - defaultVizColor: 'red', - }; - const chartTheme = { ...LIGHT_THEME, colors: chartColors }; - const domainsByGroupId = mergeYCustomDomainsByGroupId(axesSpecs, chartRotation); - const seriesDomains = computeSeriesDomains(seriesSpecs, domainsByGroupId); - const seriesColorMap = getSeriesColors( - seriesDomains.seriesCollection, - chartColors, - new Map(), - emptySeriesOverrides, - ); - const geometries = computeSeriesGeometries( - seriesSpecs, - seriesDomains.xDomain, - seriesDomains.yDomain, - seriesDomains.formattedDataSeries, - seriesColorMap, - chartTheme, - chartDimensions, - chartRotation, - axesSpecs, - false, - ); + }); + const geometries = getGeometriesFromSpecs([area, line, bar]); + expect(geometries.geometriesCounts.bars).toBe(8); expect(geometries.geometriesCounts.linePoints).toBe(8); expect(geometries.geometriesCounts.areasPoints).toBe(8); expect(geometries.geometriesCounts.lines).toBe(2); expect(geometries.geometriesCounts.areas).toBe(2); }); + test('can compute non stacked geometries indexes', () => { - const line1: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const line1 = MockSeriesSpec.line({ id: 'line1', groupId: 'group1', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Ordinal, xAccessor: 'x', yAccessors: ['y'], data: BARCHART_1Y0G, - }; - const line2: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const line2 = MockSeriesSpec.line({ id: 'line2', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Ordinal, xAccessor: 'x', yAccessors: ['y'], data: BARCHART_1Y0G, - }; - const seriesSpecs: BasicSeriesSpec[] = [line1, line2]; - const axesSpecs: AxisSpec[] = []; - const chartRotation = 0; - const chartDimensions = { width: 100, height: 100, top: 0, left: 0 }; - const chartColors = { - vizColors: ['violet', 'green', 'blue'], - defaultVizColor: 'red', - }; - const chartTheme = { ...LIGHT_THEME, colors: chartColors }; - const domainsByGroupId = mergeYCustomDomainsByGroupId(axesSpecs, chartRotation); - const seriesDomains = computeSeriesDomains(seriesSpecs, domainsByGroupId); - const seriesColorMap = getSeriesColors( - seriesDomains.seriesCollection, - chartColors, - new Map(), - emptySeriesOverrides, - ); - const geometries = computeSeriesGeometries( - seriesSpecs, - seriesDomains.xDomain, - seriesDomains.yDomain, - seriesDomains.formattedDataSeries, - seriesColorMap, - chartTheme, - chartDimensions, - chartRotation, - axesSpecs, - false, - ); + }); + const geometries = getGeometriesFromSpecs([line1, line2]); + expect(geometries.geometriesIndex.size).toBe(4); expect(geometries.geometriesIndex.find(0)?.length).toBe(2); expect(geometries.geometriesIndex.find(1)?.length).toBe(2); expect(geometries.geometriesIndex.find(2)?.length).toBe(2); expect(geometries.geometriesIndex.find(3)?.length).toBe(2); }); + test('can compute stacked geometries indexes', () => { - const line1: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const line1 = MockSeriesSpec.line({ id: 'line1', groupId: 'group1', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Ordinal, xAccessor: 'x', yAccessors: ['y'], stackAccessors: ['x'], data: BARCHART_1Y0G, - }; - const line2: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const line2 = MockSeriesSpec.line({ id: 'line2', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Ordinal, xAccessor: 'x', yAccessors: ['y'], stackAccessors: ['x'], data: BARCHART_1Y0G, - }; - const seriesSpecs: BasicSeriesSpec[] = [line1, line2]; - const axesSpecs: AxisSpec[] = []; - const chartRotation = 0; - const chartDimensions = { width: 100, height: 100, top: 0, left: 0 }; - const chartColors = { - vizColors: ['violet', 'green', 'blue'], - defaultVizColor: 'red', - }; - const chartTheme = { ...LIGHT_THEME, colors: chartColors }; - const domainsByGroupId = mergeYCustomDomainsByGroupId(axesSpecs, chartRotation); - const seriesDomains = computeSeriesDomains(seriesSpecs, domainsByGroupId); - const seriesColorMap = getSeriesColors( - seriesDomains.seriesCollection, - chartColors, - new Map(), - emptySeriesOverrides, - ); - const geometries = computeSeriesGeometries( - seriesSpecs, - seriesDomains.xDomain, - seriesDomains.yDomain, - seriesDomains.formattedDataSeries, - seriesColorMap, - chartTheme, - chartDimensions, - chartRotation, - axesSpecs, - false, - ); + }); + + const geometries = getGeometriesFromSpecs([line1, line2]); + expect(geometries.geometriesIndex.size).toBe(4); expect(geometries.geometriesIndex.find(0)?.length).toBe(2); expect(geometries.geometriesIndex.find(1)?.length).toBe(2); expect(geometries.geometriesIndex.find(2)?.length).toBe(2); expect(geometries.geometriesIndex.find(3)?.length).toBe(2); }); + test('can compute non stacked geometries counts', () => { - const area: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const area = MockSeriesSpec.area({ id: 'area', groupId: 'group1', - seriesType: SeriesTypes.Area, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const line: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const line = MockSeriesSpec.line({ id: 'line', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const bar: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const bar = MockSeriesSpec.bar({ id: 'bar', groupId: 'group2', - seriesType: SeriesTypes.Bar, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', @@ -588,36 +464,9 @@ describe('Chart State utils', () => { displayValueSettings: { showValueLabel: true, }, - }; - const seriesSpecs: BasicSeriesSpec[] = [area, line, bar]; - const axesSpecs: AxisSpec[] = []; - const chartRotation = 0; - const chartDimensions = { width: 100, height: 100, top: 0, left: 0 }; - const chartColors = { - vizColors: ['violet', 'green', 'blue'], - defaultVizColor: 'red', - }; - const chartTheme = { ...LIGHT_THEME, colors: chartColors }; - const domainsByGroupId = mergeYCustomDomainsByGroupId(axesSpecs, chartRotation); - const seriesDomains = computeSeriesDomains(seriesSpecs, domainsByGroupId); - const seriesColorMap = getSeriesColors( - seriesDomains.seriesCollection, - chartColors, - new Map(), - emptySeriesOverrides, - ); - const geometries = computeSeriesGeometries( - seriesSpecs, - seriesDomains.xDomain, - seriesDomains.yDomain, - seriesDomains.formattedDataSeries, - seriesColorMap, - chartTheme, - chartDimensions, - chartRotation, - axesSpecs, - false, - ); + }); + const geometries = getGeometriesFromSpecs([area, line, bar]); + expect(geometries.geometriesCounts.bars).toBe(8); expect(geometries.geometriesCounts.linePoints).toBe(8); expect(geometries.geometriesCounts.areasPoints).toBe(8); @@ -625,74 +474,39 @@ describe('Chart State utils', () => { expect(geometries.geometriesCounts.areas).toBe(2); }); test('can compute line geometries counts', () => { - const line1: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const line1 = MockSeriesSpec.line({ id: 'line1', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const line2: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const line2 = MockSeriesSpec.line({ id: 'line2', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const line3: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const line3 = MockSeriesSpec.line({ id: 'line3', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const seriesSpecs: BasicSeriesSpec[] = [line1, line2, line3]; - const axesSpecs: AxisSpec[] = []; - const chartRotation = 0; - const chartDimensions = { width: 100, height: 100, top: 0, left: 0 }; - const chartColors = { - vizColors: ['violet', 'green', 'blue'], - defaultVizColor: 'red', - }; - const chartTheme = { ...LIGHT_THEME, colors: chartColors }; - const domainsByGroupId = mergeYCustomDomainsByGroupId(axesSpecs, chartRotation); - const seriesDomains = computeSeriesDomains(seriesSpecs, domainsByGroupId); - const seriesColorMap = getSeriesColors( - seriesDomains.seriesCollection, - chartColors, - new Map(), - emptySeriesOverrides, - ); - const geometries = computeSeriesGeometries( - seriesSpecs, - seriesDomains.xDomain, - seriesDomains.yDomain, - seriesDomains.formattedDataSeries, - seriesColorMap, - chartTheme, - chartDimensions, - chartRotation, - axesSpecs, - false, - ); + }); + + const geometries = getGeometriesFromSpecs([line1, line2, line3]); + expect(geometries.geometriesCounts.bars).toBe(0); expect(geometries.geometriesCounts.linePoints).toBe(24); expect(geometries.geometriesCounts.areasPoints).toBe(0); @@ -700,74 +514,39 @@ describe('Chart State utils', () => { expect(geometries.geometriesCounts.areas).toBe(0); }); test('can compute area geometries counts', () => { - const area1: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const area1 = MockSeriesSpec.area({ id: 'area1', groupId: 'group2', - seriesType: SeriesTypes.Area, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const area2: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const area2 = MockSeriesSpec.area({ id: 'area2', groupId: 'group2', - seriesType: SeriesTypes.Area, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const area3: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const area3 = MockSeriesSpec.area({ id: 'area3', groupId: 'group2', - seriesType: SeriesTypes.Area, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const seriesSpecs: BasicSeriesSpec[] = [area1, area2, area3]; - const axesSpecs: AxisSpec[] = []; - const chartRotation = 0; - const chartDimensions = { width: 100, height: 100, top: 0, left: 0 }; - const chartColors = { - vizColors: ['violet', 'green', 'blue'], - defaultVizColor: 'red', - }; - const chartTheme = { ...LIGHT_THEME, colors: chartColors }; - const domainsByGroupId = mergeYCustomDomainsByGroupId(axesSpecs, chartRotation); - const seriesDomains = computeSeriesDomains(seriesSpecs, domainsByGroupId); - const seriesColorMap = getSeriesColors( - seriesDomains.seriesCollection, - chartColors, - new Map(), - emptySeriesOverrides, - ); - const geometries = computeSeriesGeometries( - seriesSpecs, - seriesDomains.xDomain, - seriesDomains.yDomain, - seriesDomains.formattedDataSeries, - seriesColorMap, - chartTheme, - chartDimensions, - chartRotation, - axesSpecs, - false, - ); + }); + + const geometries = getGeometriesFromSpecs([area1, area2, area3]); + expect(geometries.geometriesCounts.bars).toBe(0); expect(geometries.geometriesCounts.linePoints).toBe(0); expect(geometries.geometriesCounts.areasPoints).toBe(24); @@ -775,12 +554,9 @@ describe('Chart State utils', () => { expect(geometries.geometriesCounts.areas).toBe(6); }); test('can compute line geometries with custom style', () => { - const line1: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const line1 = MockSeriesSpec.line({ id: 'line1', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', @@ -795,69 +571,37 @@ describe('Chart State utils', () => { }, }, data: BARCHART_1Y1G, - }; - const line2: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const line2 = MockSeriesSpec.line({ id: 'line2', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const line3: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const line3 = MockSeriesSpec.line({ id: 'line3', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const seriesSpecs: BasicSeriesSpec[] = [line1, line2, line3]; - const axesSpecs: AxisSpec[] = []; - const chartRotation = 0; - const chartDimensions = { width: 100, height: 100, top: 0, left: 0 }; - const chartColors = { - vizColors: ['violet', 'green', 'blue'], - defaultVizColor: 'red', - }; - const chartTheme = { ...LIGHT_THEME, colors: chartColors }; - const domainsByGroupId = mergeYCustomDomainsByGroupId(axesSpecs, chartRotation); - const seriesDomains = computeSeriesDomains(seriesSpecs, domainsByGroupId); - const seriesColorMap = getSeriesColors( - seriesDomains.seriesCollection, - chartColors, - new Map(), - emptySeriesOverrides, - ); - const geometries = computeSeriesGeometries( - seriesSpecs, - seriesDomains.xDomain, - seriesDomains.yDomain, - seriesDomains.formattedDataSeries, - seriesColorMap, - chartTheme, - chartDimensions, - chartRotation, - axesSpecs, - false, - ); - expect(geometries.geometries.lines[0].color).toBe('violet'); - expect(geometries.geometries.lines[0].seriesLineStyle).toEqual({ + }); + + const geometries = getGeometriesFromSpecs([line1, line2, line3]); + + expect(geometries.geometries.lines[0].value.color).toBe('violet'); + expect(geometries.geometries.lines[0].value.seriesLineStyle).toEqual({ visible: true, strokeWidth: 100, // the override strokeWidth opacity: 1, }); - expect(geometries.geometries.lines[0].seriesPointStyle).toEqual({ + expect(geometries.geometries.lines[0].value.seriesPointStyle).toEqual({ visible: true, fill: 'green', // the override strokeWidth opacity: 1, @@ -866,12 +610,9 @@ describe('Chart State utils', () => { }); }); test('can compute area geometries with custom style', () => { - const area1: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const area1 = MockSeriesSpec.area({ id: 'area1', groupId: 'group2', - seriesType: SeriesTypes.Area, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', @@ -890,74 +631,42 @@ describe('Chart State utils', () => { opacity: 0.2, }, }, - }; - const area2: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const area2 = MockSeriesSpec.area({ id: 'area2', groupId: 'group2', - seriesType: SeriesTypes.Area, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const area3: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const area3 = MockSeriesSpec.area({ id: 'area3', groupId: 'group2', - seriesType: SeriesTypes.Area, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const seriesSpecs: BasicSeriesSpec[] = [area1, area2, area3]; - const axesSpecs: AxisSpec[] = []; - const chartRotation = 0; - const chartDimensions = { width: 100, height: 100, top: 0, left: 0 }; - const chartColors = { - vizColors: ['violet', 'green', 'blue'], - defaultVizColor: 'red', - }; - const chartTheme = { ...LIGHT_THEME, colors: chartColors }; - const domainsByGroupId = mergeYCustomDomainsByGroupId(axesSpecs, chartRotation); - const seriesDomains = computeSeriesDomains(seriesSpecs, domainsByGroupId); - const seriesColorMap = getSeriesColors( - seriesDomains.seriesCollection, - chartColors, - new Map(), - emptySeriesOverrides, - ); - const geometries = computeSeriesGeometries( - seriesSpecs, - seriesDomains.xDomain, - seriesDomains.yDomain, - seriesDomains.formattedDataSeries, - seriesColorMap, - chartTheme, - chartDimensions, - chartRotation, - axesSpecs, - false, - ); - expect(geometries.geometries.areas[0].color).toBe('violet'); - expect(geometries.geometries.areas[0].seriesAreaStyle).toEqual({ + }); + + const geometries = getGeometriesFromSpecs([area1, area2, area3]); + + expect(geometries.geometries.areas[0].value.color).toBe('violet'); + expect(geometries.geometries.areas[0].value.seriesAreaStyle).toEqual({ visible: true, fill: 'area-fill-custom-color', opacity: 0.2, }); - expect(geometries.geometries.areas[0].seriesAreaLineStyle).toEqual({ + expect(geometries.geometries.areas[0].value.seriesAreaLineStyle).toEqual({ visible: true, strokeWidth: 100, opacity: 1, }); - expect(geometries.geometries.areas[0].seriesPointStyle).toEqual({ + expect(geometries.geometries.areas[0].value.seriesPointStyle).toEqual({ visible: false, fill: 'point-fill-custom-color', // the override strokeWidth opacity: 1, @@ -966,74 +675,39 @@ describe('Chart State utils', () => { }); }); test('can compute bars geometries counts', () => { - const bars1: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const bars1 = MockSeriesSpec.bar({ id: 'bars1', groupId: 'group2', - seriesType: SeriesTypes.Bar, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const bars2: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const bars2 = MockSeriesSpec.bar({ id: 'bars2', groupId: 'group2', - seriesType: SeriesTypes.Bar, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const bars3: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const bars3 = MockSeriesSpec.bar({ id: 'bars3', groupId: 'group2', - seriesType: SeriesTypes.Bar, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const seriesSpecs: BasicSeriesSpec[] = [bars1, bars2, bars3]; - const axesSpecs: AxisSpec[] = []; - const chartRotation = 0; - const chartDimensions = { width: 100, height: 100, top: 0, left: 0 }; - const chartColors = { - vizColors: ['violet', 'green', 'blue'], - defaultVizColor: 'red', - }; - const chartTheme = { ...LIGHT_THEME, colors: chartColors }; - const domainsByGroupId = mergeYCustomDomainsByGroupId(axesSpecs, chartRotation); - const seriesDomains = computeSeriesDomains(seriesSpecs, domainsByGroupId); - const seriesColorMap = getSeriesColors( - seriesDomains.seriesCollection, - chartColors, - new Map(), - emptySeriesOverrides, - ); - const geometries = computeSeriesGeometries( - seriesSpecs, - seriesDomains.xDomain, - seriesDomains.yDomain, - seriesDomains.formattedDataSeries, - seriesColorMap, - chartTheme, - chartDimensions, - chartRotation, - axesSpecs, - false, - ); + }); + + const geometries = getGeometriesFromSpecs([bars1, bars2, bars3]); + expect(geometries.geometriesCounts.bars).toBe(24); expect(geometries.geometriesCounts.linePoints).toBe(0); expect(geometries.geometriesCounts.areasPoints).toBe(0); @@ -1041,111 +715,33 @@ describe('Chart State utils', () => { expect(geometries.geometriesCounts.areas).toBe(0); }); test('can compute the bar offset in mixed charts', () => { - const line1: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const line1 = MockSeriesSpec.line({ id: 'line1', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const bar1: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const bar1 = MockSeriesSpec.bar({ id: 'line3', groupId: 'group2', - seriesType: SeriesTypes.Bar, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const seriesSpecs: BasicSeriesSpec[] = [line1, bar1]; - const axesSpecs: AxisSpec[] = []; - const chartRotation = 0; - const chartDimensions = { width: 100, height: 100, top: 0, left: 0 }; - const chartColors = { - vizColors: ['violet', 'green', 'blue'], - defaultVizColor: 'red', - }; - const chartTheme = { - ...LIGHT_THEME, - scales: { - barsPadding: 0, - histogramPadding: 0, - }, - }; - const domainsByGroupId = mergeYCustomDomainsByGroupId(axesSpecs, chartRotation); - const seriesDomains = computeSeriesDomains(seriesSpecs, domainsByGroupId); - const seriesColorMap = getSeriesColors( - seriesDomains.seriesCollection, - chartColors, - new Map(), - emptySeriesOverrides, - ); - const geometries = computeSeriesGeometries( - seriesSpecs, - seriesDomains.xDomain, - seriesDomains.yDomain, - seriesDomains.formattedDataSeries, - seriesColorMap, - chartTheme, - chartDimensions, - chartRotation, - axesSpecs, - false, - ); - expect(geometries.geometries.bars[0].x).toBe(0); + }); + + const geometries = getGeometriesFromSpecs([line1, bar1]); + + expect(geometries.geometries.bars[0].value[0].x).toBe(0); }); }); - test.skip('can merge geometry indexes', () => { - const map1 = new Map(); - map1.set('a', [ - { - radius: 10, - x: 0, - y: 0, - color: '#1EA593', - value: { x: 0, y: 5, accessor: BandedAccessorType.Y1, mark: null, datum: { x: 0, y: 5 } }, - transform: { x: 0, y: 0 }, - seriesIdentifier: { - specId: 'line1', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [], - key: '', - }, - }, - ]); - const map2 = new Map(); - map2.set('a', [ - { - radius: 10, - x: 0, - y: 175.8, - color: '#2B70F7', - value: { x: 0, y: 2, accessor: BandedAccessorType.Y1, mark: null, datum: { x: 0, y: 5 } }, - transform: { x: 0, y: 0 }, - seriesIdentifier: { - specId: 'line2', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [], - key: '', - }, - }, - ]); - // const merged = mergeGeometriesIndexes(map1, map2); - // expect(merged.get('a')).toBeDefined(); - // expect(merged.get('a')?.length).toBe(2); - }); + test('can compute xScaleOffset dependent on histogram mode', () => { const domain = [0, 10]; const range: [number, number] = [0, 100]; @@ -1168,25 +764,19 @@ describe('Chart State utils', () => { expect(computeXScaleOffset(scale, histogramModeEnabled, HistogramModeAlignments.End)).toBe(-5); }); test('can determine if histogram mode is enabled', () => { - const area: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const area = MockSeriesSpec.area({ id: 'area', groupId: 'group1', - seriesType: SeriesTypes.Area, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const line: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const line = MockSeriesSpec.line({ id: 'line', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', @@ -1194,13 +784,10 @@ describe('Chart State utils', () => { splitSeriesAccessors: ['g'], stackAccessors: ['x'], data: BARCHART_1Y1G, - }; - const basicBar: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const basicBar = MockSeriesSpec.bar({ id: 'bar', groupId: 'group2', - seriesType: SeriesTypes.Bar, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', @@ -1208,22 +795,17 @@ describe('Chart State utils', () => { splitSeriesAccessors: ['g'], stackAccessors: ['x'], data: BARCHART_1Y1G, - }; - const histogramBar: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const histogramBar = MockSeriesSpec.histogramBar({ id: 'histo', groupId: 'group2', - seriesType: SeriesTypes.Bar, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], - stackAccessors: ['x'], data: BARCHART_1Y1G, - enableHistogramMode: true, - }; + }); let seriesMap: BasicSeriesSpec[] = [area, line, basicBar, histogramBar]; expect(isHistogramModeEnabled(seriesMap)).toBe(true); @@ -1237,25 +819,19 @@ describe('Chart State utils', () => { test('can set the bar series accessors dependent on histogram mode', () => { const isNotHistogramEnabled = false; const isHistogramEnabled = true; - const area: AreaSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const area = MockSeriesSpec.area({ id: 'area', groupId: 'group1', - seriesType: SeriesTypes.Area, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], data: BARCHART_1Y1G, - }; - const line: LineSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const line = MockSeriesSpec.line({ id: 'line', groupId: 'group2', - seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', @@ -1263,13 +839,10 @@ describe('Chart State utils', () => { splitSeriesAccessors: ['g'], stackAccessors: ['x'], data: BARCHART_1Y1G, - }; - const bar: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + }); + const bar = MockSeriesSpec.bar({ id: 'bar', groupId: 'group2', - seriesType: SeriesTypes.Bar, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', @@ -1277,7 +850,7 @@ describe('Chart State utils', () => { splitSeriesAccessors: ['g'], stackAccessors: ['foo'], data: BARCHART_1Y1G, - }; + }); const seriesMap = new Map([ [area.id, area], [line.id, line], @@ -1292,19 +865,16 @@ describe('Chart State utils', () => { setBarSeriesAccessors(isHistogramEnabled, seriesMap); expect(bar.stackAccessors).toEqual(['foo', 'g']); // add another bar - const bar2: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const bar2 = MockSeriesSpec.bar({ id: 'bar2', groupId: 'group2', - seriesType: SeriesTypes.Bar, yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['bar'], data: BARCHART_1Y1G, - }; + }); seriesMap.set(bar2.id, bar2); setBarSeriesAccessors(isHistogramEnabled, seriesMap); expect(bar2.stackAccessors).toEqual(['y', 'bar']); diff --git a/src/chart_types/xy_chart/state/utils/utils.ts b/src/chart_types/xy_chart/state/utils/utils.ts index 7d5300d194..b2ace9fa1d 100644 --- a/src/chart_types/xy_chart/state/utils/utils.ts +++ b/src/chart_types/xy_chart/state/utils/utils.ts @@ -19,31 +19,40 @@ import { SeriesKey, SeriesIdentifier } from '../../../../commons/series_id'; import { Scale } from '../../../../scales'; +import { GroupBySpec } from '../../../../specs'; import { OrderBy } from '../../../../specs/settings'; import { mergePartial, Rotation, Color, isUniqueArray } from '../../../../utils/commons'; import { CurveType } from '../../../../utils/curves'; -import { Dimensions } from '../../../../utils/dimensions'; +import { Dimensions, Size } from '../../../../utils/dimensions'; import { Domain } from '../../../../utils/domain'; -import { PointGeometry, BarGeometry, AreaGeometry, LineGeometry, BubbleGeometry } from '../../../../utils/geometry'; +import { + PointGeometry, + BarGeometry, + AreaGeometry, + LineGeometry, + BubbleGeometry, + PerPanel, +} from '../../../../utils/geometry'; import { GroupId, SpecId } from '../../../../utils/ids'; import { ColorConfig, Theme } from '../../../../utils/themes/theme'; -import { XDomain, YDomain } from '../../domains/types'; +import { getPredicateFn, Predicate } from '../../../heatmap/utils/commons'; +import { XDomain } from '../../domains/types'; import { mergeXDomain } from '../../domains/x_domain'; -import { mergeYDomain, splitSpecsByGroupId } from '../../domains/y_domain'; +import { isStackedSpec, mergeYDomain } from '../../domains/y_domain'; import { renderArea, renderBars, renderLine, renderBubble, isDatumFilled } from '../../rendering/rendering'; import { defaultTickFormatter } from '../../utils/axis_utils'; import { fillSeries } from '../../utils/fill_series'; +import { groupBy } from '../../utils/group_data_series'; import { IndexedGeometryMap } from '../../utils/indexed_geometry_map'; -import { computeXScale, computeYScales, countBarsInCluster } from '../../utils/scales'; +import { computeXScale, computeYScales } from '../../utils/scales'; import { DataSeries, SeriesCollectionValue, getSeriesIndex, - FormattedDataSeries, - getFormattedDataseries, - getDataSeriesBySpecId, - getSeriesKey, + getFormattedDataSeries, + getDataSeriesFromSpecs, XYChartSeriesIdentifier, + getSeriesKey, } from '../../utils/series'; import { AxisSpec, @@ -59,15 +68,13 @@ import { FitConfig, isBubbleSeriesSpec, YDomainRange, - SeriesTypes, StackMode, } from '../../utils/specs'; +import { SmallMultipleScales } from '../selectors/compute_small_multiple_scales'; +import { isHorizontalRotation } from './common'; import { getSpecsById, getAxesSpecForSpecId } from './spec'; import { SeriesDomainsAndData, ComputedGeometries, GeometriesCounts, Transform, LastValues } from './types'; -export const MAX_ANIMATABLE_BARS = 300; -export const MAX_ANIMATABLE_LINES_AREA_POINTS = 600; - /** * Adds or removes series from array or series * @param series @@ -90,10 +97,9 @@ export function updateDeselectedDataSeries( } /** - * Return map assocition between `seriesKey` and only the custom colors string + * Return map association between `seriesKey` and only the custom colors string * @param seriesSpecs * @param seriesCollection - * @param seriesColorOverrides color override from legend * @internal */ export function getCustomSeriesColors( @@ -130,76 +136,41 @@ export function getCustomSeriesColors( return updatedCustomSeriesColors; } -function getLastValues( - formattedDataSeries: { - stacked: FormattedDataSeries[]; - nonStacked: FormattedDataSeries[]; - }, - xDomain: XDomain, -): Map { +function getLastValues(dataSeries: DataSeries[], xDomain: XDomain): Map { const lastValues = new Map(); // we need to get the latest - formattedDataSeries.stacked.forEach(({ dataSeries, stackMode }) => { - dataSeries.forEach((series) => { - if (series.data.length === 0) { - return; - } - - const last = series.data[series.data.length - 1]; - if (!last) { - return; - } - if (isDatumFilled(last)) { - return; - } - - if (last.x !== xDomain.domain[xDomain.domain.length - 1]) { - // we have a dataset that is not filled with all x values - // and the last value of the series is not the last value for every series - // let's skip it - return; - } - - const { y0, y1, initialY0, initialY1 } = last; - const seriesKey = getSeriesKey(series as XYChartSeriesIdentifier); - - if (stackMode === StackMode.Percentage) { - const y1InPercentage = y1 === null || y0 === null ? null : y1 - y0; - lastValues.set(seriesKey, { y0, y1: y1InPercentage }); - return; - } - if (initialY0 !== null || initialY1 !== null) { - lastValues.set(seriesKey, { y0: initialY0, y1: initialY1 }); - } - }); - }); + dataSeries.forEach((series) => { + if (series.data.length === 0) { + return; + } - formattedDataSeries.nonStacked.forEach(({ dataSeries }) => { - dataSeries.forEach((series) => { - if (series.data.length === 0) { - return; - } - const last = series.data[series.data.length - 1]; - if (!last) { - return; - } - if (isDatumFilled(last)) { - return; - } + const last = series.data[series.data.length - 1]; + if (!last) { + return; + } + if (isDatumFilled(last)) { + return; + } - if (last.x !== xDomain.domain[xDomain.domain.length - 1]) { - // we have a dataset that is not filled with all x values - // and the last value of the series is not the last value for every series - // let's skip it - return; - } + if (last.x !== xDomain.domain[xDomain.domain.length - 1]) { + // we have a dataset that is not filled with all x values + // and the last value of the series is not the last value for every series + // let's skip it + return; + } - const { initialY1, initialY0 } = last; - const seriesKey = getSeriesKey(series as XYChartSeriesIdentifier); + const { y0, y1, initialY0, initialY1 } = last; + const seriesKey = getSeriesKey(series as XYChartSeriesIdentifier, series.groupId); + if (series.stackMode === StackMode.Percentage) { + const y1InPercentage = y1 === null || y0 === null ? null : y1 - y0; + lastValues.set(seriesKey, { y0, y1: y1InPercentage }); + return; + } + if (initialY0 !== null || initialY1 !== null) { lastValues.set(seriesKey, { y0: initialY0, y1: initialY1 }); - }); + } }); return lastValues; } @@ -215,6 +186,7 @@ function getLastValues( * @param enableVislibSeriesSort is optional; if not specified in , * then all series will be factored into computations. Otherwise, selectedDataSeries * is used to restrict the computation for just the selected series + * @param smallMultiples * @returns `SeriesDomainsAndData` * @internal */ @@ -225,39 +197,27 @@ export function computeSeriesDomains( customXDomain?: DomainRange | Domain, orderOrdinalBinsBy?: OrderBy, enableVislibSeriesSort?: boolean, + smallMultiples?: { vertical?: GroupBySpec; horizontal?: GroupBySpec }, ): SeriesDomainsAndData { - const { dataSeriesBySpecId, xValues, seriesCollection, fallbackScale } = getDataSeriesBySpecId( + const { dataSeries, xValues, seriesCollection, fallbackScale, smHValues, smVValues } = getDataSeriesFromSpecs( seriesSpecs, deselectedDataSeries, orderOrdinalBinsBy, enableVislibSeriesSort, + smallMultiples, ); // compute the x domain merging any custom domain const xDomain = mergeXDomain(seriesSpecs, xValues, customXDomain, fallbackScale); - const specsByGroupIds = splitSpecsByGroupId(seriesSpecs); - // fill series with missing x values - const filledDataSeriesBySpecId = fillSeries( - dataSeriesBySpecId, - xValues, - seriesSpecs, - xDomain.scaleType, - specsByGroupIds, - ); + const filledDataSeries = fillSeries(dataSeries, xValues, xDomain.scaleType); - const formattedDataSeries = getFormattedDataseries( - filledDataSeriesBySpecId, - xValues, - xDomain.scaleType, - seriesSpecs, - specsByGroupIds, - ); + const formattedDataSeries = getFormattedDataSeries(seriesSpecs, filledDataSeries, xValues, xDomain.scaleType); // let's compute the yDomain after computing all stacked values - const yDomain = mergeYDomain(formattedDataSeries, seriesSpecs, customYDomainsByGroupId); + const yDomain = mergeYDomain(formattedDataSeries, customYDomainsByGroupId); - // we need to get the last values from the formatted dataseries + // we need to get the last values from the formattedDataSeries // because we change the format if we are on percentage mode const lastValues = getLastValues(formattedDataSeries, xDomain); const updatedSeriesCollection = new Map(); @@ -270,9 +230,18 @@ export function computeSeriesDomains( updatedSeriesCollection.set(key, updatedColorSet); }); + // sort small multiples values + const horizontalPredicate = smallMultiples?.horizontal?.sort ?? Predicate.DataIndex; + const smHDomain = [...smHValues].sort(getPredicateFn(horizontalPredicate)); + + const verticalPredicate = smallMultiples?.vertical?.sort ?? Predicate.DataIndex; + const smVDomain = [...smVValues].sort(getPredicateFn(verticalPredicate)); + return { xDomain, yDomain, + smHDomain, + smVDomain, formattedDataSeries, seriesCollection: updatedSeriesCollection, }; @@ -281,156 +250,79 @@ export function computeSeriesDomains( /** @internal */ export function computeSeriesGeometries( seriesSpecs: BasicSeriesSpec[], - xDomain: XDomain, - yDomain: YDomain[], - formattedDataSeries: { - stacked: FormattedDataSeries[]; - nonStacked: FormattedDataSeries[]; - }, + { xDomain, yDomain, formattedDataSeries }: SeriesDomainsAndData, seriesColorMap: Map, chartTheme: Theme, - chartDims: Dimensions, chartRotation: Rotation, axesSpecs: AxisSpec[], + smallMultiplesScales: SmallMultipleScales, enableHistogramMode: boolean, ): ComputedGeometries { const chartColors: ColorConfig = chartTheme.colors; - const barsPadding = enableHistogramMode ? chartTheme.scales.histogramPadding : chartTheme.scales.barsPadding; - const width = [0, 180].includes(chartRotation) ? chartDims.width : chartDims.height; - const height = [0, 180].includes(chartRotation) ? chartDims.height : chartDims.width; - // const { width, height } = chartDims; - const { stacked, nonStacked } = formattedDataSeries; + const barDataSeries = formattedDataSeries.filter(({ spec }) => isBarSeriesSpec(spec)); + // compute max bar in cluster per panel + const dataSeriesGroupedByPanel = groupBy( + barDataSeries, + ['smVerticalAccessorValue', 'smHorizontalAccessorValue'], + false, + ); - // compute how many series are clustered - const { stackedBarsInCluster, totalBarsInCluster } = countBarsInCluster(stacked, nonStacked); - // compute scales - const xScale = computeXScale({ - xDomain, - totalBarsInCluster, - range: [0, width], - barsPadding, - enableHistogramMode, - }); - const yScales = computeYScales({ yDomains: yDomain, range: [height, 0] }); + const barIndexByPanel = Object.keys(dataSeriesGroupedByPanel).reduce>((acc, panelKey) => { + const panelBars = dataSeriesGroupedByPanel[panelKey]; + const barDataSeriesByBarIndex = groupBy( + panelBars, + (d) => { + return getBarIndexKey(d, enableHistogramMode); + }, + false, + ); - // compute colors + acc[panelKey] = Object.keys(barDataSeriesByBarIndex); + return acc; + }, {}); - // compute geometries - const points: PointGeometry[] = []; - const areas: AreaGeometry[] = []; - const bars: BarGeometry[] = []; - const lines: LineGeometry[] = []; - const bubbles: BubbleGeometry[] = []; - const geometriesIndex = new IndexedGeometryMap(); - let orderIndex = 0; - const geometriesCounts: GeometriesCounts = { - points: 0, - bars: 0, - areas: 0, - areasPoints: 0, - lines: 0, - linePoints: 0, - bubbles: 0, - bubblePoints: 0, - }; - - formattedDataSeries.stacked.forEach((dataSeriesGroup) => { - const { groupId, dataSeries, counts, stackMode } = dataSeriesGroup; - const yScale = yScales.get(groupId); - if (!yScale) { - return; - } + const { horizontal, vertical } = smallMultiplesScales; - const geometries = renderGeometries( - orderIndex, - totalBarsInCluster, - true, - dataSeries, - xScale, - yScale, - seriesSpecs, - seriesColorMap, - chartColors.defaultVizColor, - axesSpecs, - chartTheme, - enableHistogramMode, - chartRotation, - stackMode, - ); - orderIndex = counts[SeriesTypes.Bar] > 0 ? orderIndex + 1 : orderIndex; - areas.push(...geometries.areas); - lines.push(...geometries.lines); - bars.push(...geometries.bars); - bubbles.push(...geometries.bubbles); - points.push(...geometries.points); - geometriesIndex.merge(geometries.indexedGeometryMap); - // update counts - geometriesCounts.points += geometries.geometriesCounts.points; - geometriesCounts.bars += geometries.geometriesCounts.bars; - geometriesCounts.areas += geometries.geometriesCounts.areas; - geometriesCounts.areasPoints += geometries.geometriesCounts.areasPoints; - geometriesCounts.lines += geometries.geometriesCounts.lines; - geometriesCounts.linePoints += geometries.geometriesCounts.linePoints; - geometriesCounts.bubbles += geometries.geometriesCounts.bubbles; - geometriesCounts.bubblePoints += geometries.geometriesCounts.bubblePoints; + const yScales = computeYScales({ + yDomains: yDomain, + range: [isHorizontalRotation(chartRotation) ? vertical.bandwidth : horizontal.bandwidth, 0], }); - orderIndex = 0; - formattedDataSeries.nonStacked.forEach((dataSeriesGroup) => { - const { groupId, dataSeries, counts } = dataSeriesGroup; - const yScale = yScales.get(groupId); - if (!yScale) { - return; - } - const geometries = renderGeometries( - stackedBarsInCluster + orderIndex, - totalBarsInCluster, - false, - dataSeries, - xScale, - yScale, - seriesSpecs, - seriesColorMap, - chartColors.defaultVizColor, - axesSpecs, - chartTheme, - enableHistogramMode, - chartRotation, - ); - orderIndex = counts[SeriesTypes.Bar] > 0 ? orderIndex + counts[SeriesTypes.Bar] : orderIndex; - - areas.push(...geometries.areas); - lines.push(...geometries.lines); - bars.push(...geometries.bars); - bubbles.push(...geometries.bubbles); - points.push(...geometries.points); - - geometriesIndex.merge(geometries.indexedGeometryMap); - // update counts - geometriesCounts.points += geometries.geometriesCounts.points; - geometriesCounts.bars += geometries.geometriesCounts.bars; - geometriesCounts.areas += geometries.geometriesCounts.areas; - geometriesCounts.areasPoints += geometries.geometriesCounts.areasPoints; - geometriesCounts.lines += geometries.geometriesCounts.lines; - geometriesCounts.linePoints += geometries.geometriesCounts.linePoints; - geometriesCounts.bubbles += geometries.geometriesCounts.bubbles; - geometriesCounts.bubblePoints += geometries.geometriesCounts.bubblePoints; + const computedGeoms = renderGeometries( + formattedDataSeries, + xDomain, + yScales, + vertical, + horizontal, + barIndexByPanel, + seriesSpecs, + seriesColorMap, + chartColors.defaultVizColor, + axesSpecs, + chartTheme, + enableHistogramMode, + chartRotation, + ); + + const totalBarsInCluster = Object.values(barIndexByPanel).reduce((acc, curr) => { + return Math.max(acc, curr.length); + }, 0); + + const xScale = computeXScale({ + xDomain, + totalBarsInCluster, + range: [0, isHorizontalRotation(chartRotation) ? horizontal.bandwidth : vertical.bandwidth], + barsPadding: enableHistogramMode ? chartTheme.scales.histogramPadding : chartTheme.scales.barsPadding, + enableHistogramMode, }); + return { scales: { xScale, yScales, }, - geometries: { - points, - areas, - bars, - lines, - bubbles, - }, - geometriesIndex, - geometriesCounts, + ...computedGeoms, }; } @@ -486,37 +378,28 @@ export function computeXScaleOffset( } function renderGeometries( - indexOffset: number, - clusteredCount: number, - isStacked: boolean, dataSeries: DataSeries[], - xScale: Scale, - yScale: Scale, + xDomain: XDomain, + yScales: Map, + smVScale: Scale, + smHScale: Scale, + barIndexOrderPerPanel: Record, seriesSpecs: BasicSeriesSpec[], seriesColorsMap: Map, defaultColor: string, axesSpecs: AxisSpec[], chartTheme: Theme, enableHistogramMode: boolean, - chartRotation: number, - stackMode?: StackMode, -): { - points: PointGeometry[]; - bars: BarGeometry[]; - areas: AreaGeometry[]; - lines: LineGeometry[]; - bubbles: BubbleGeometry[]; - indexedGeometryMap: IndexedGeometryMap; - geometriesCounts: GeometriesCounts; -} { + chartRotation: Rotation, +): Omit { const len = dataSeries.length; let i; const points: PointGeometry[] = []; - const bars: BarGeometry[] = []; - const areas: AreaGeometry[] = []; - const lines: LineGeometry[] = []; - const bubbles: BubbleGeometry[] = []; - const indexedGeometryMap = new IndexedGeometryMap(); + const bars: Array> = []; + const areas: Array> = []; + const lines: Array> = []; + const bubbles: Array> = []; + const geometriesIndex = new IndexedGeometryMap(); const isMixedChart = isUniqueArray(seriesSpecs, ({ seriesType }) => seriesType) && seriesSpecs.length > 1; const fallBackTickFormatter = seriesSpecs.find(({ tickFormat }) => tickFormat)?.tickFormat ?? defaultTickFormatter; const geometriesCounts: GeometriesCounts = { @@ -529,18 +412,52 @@ function renderGeometries( bubbles: 0, bubblePoints: 0, }; - let barIndexOffset = 0; + const barsPadding = enableHistogramMode ? chartTheme.scales.histogramPadding : chartTheme.scales.barsPadding; + for (i = 0; i < len; i++) { const ds = dataSeries[i]; const spec = getSpecsById(seriesSpecs, ds.specId); if (spec === undefined) { continue; } + // compute the y scale + const yScale = yScales.get(ds.groupId); + if (!yScale) { + continue; + } + // compute the panel unique key + const barPanelKey = [ds.smVerticalAccessorValue, ds.smHorizontalAccessorValue].join('|'); + const barIndexOrder = barIndexOrderPerPanel[barPanelKey]; + // compute x scale + const xScale = computeXScale({ + xDomain, + totalBarsInCluster: barIndexOrder?.length ?? 0, + range: [0, isHorizontalRotation(chartRotation) ? smHScale.bandwidth : smVScale.bandwidth], + barsPadding, + enableHistogramMode, + }); - const color = seriesColorsMap.get(getSeriesKey(ds)) || defaultColor; + const { stackMode } = ds; + + const leftPos = smHScale.scale(ds.smHorizontalAccessorValue) || 0; + const topPos = smVScale.scale(ds.smVerticalAccessorValue) || 0; + const panel: Dimensions = { + width: smHScale.bandwidth, + height: smVScale.bandwidth, + top: topPos, + left: leftPos, + }; + + const color = seriesColorsMap.get(ds.key) || defaultColor; if (isBarSeriesSpec(spec)) { - const shift = isStacked ? indexOffset : indexOffset + barIndexOffset; + const key = getBarIndexKey(ds, enableHistogramMode); + const shift = barIndexOrder.indexOf(key); + + if (shift === -1) { + // skip bar dataSeries if index is not available + continue; + } const barSeriesStyle = mergePartial(chartTheme.barSeriesStyle, spec.barSeriesStyle, { mergeOptionalPartialValues: true, }); @@ -557,6 +474,7 @@ function renderGeometries( ds, xScale, yScale, + panel, color, barSeriesStyle, displayValueSettings, @@ -565,22 +483,27 @@ function renderGeometries( stackMode, chartRotation, ); - indexedGeometryMap.merge(renderedBars.indexedGeometryMap); - bars.push(...renderedBars.barGeometries); + geometriesIndex.merge(renderedBars.indexedGeometryMap); + bars.push({ + panel, + value: renderedBars.barGeometries, + }); geometriesCounts.bars += renderedBars.barGeometries.length; - barIndexOffset += 1; } else if (isBubbleSeriesSpec(spec)) { - const bubbleShift = clusteredCount > 0 ? clusteredCount : 1; + const bubbleShift = barIndexOrder && barIndexOrder.length > 0 ? barIndexOrder.length : 1; const bubbleSeriesStyle = spec.bubbleSeriesStyle ? mergePartial(chartTheme.bubbleSeriesStyle, spec.bubbleSeriesStyle, { mergeOptionalPartialValues: true }) : chartTheme.bubbleSeriesStyle; + const xScaleOffset = computeXScaleOffset(xScale, enableHistogramMode); const renderedBubbles = renderBubble( (xScale.bandwidth * bubbleShift) / 2, ds, xScale, yScale, color, + panel, isBandedSpec(spec.y0Accessors), + xScaleOffset, bubbleSeriesStyle, { enabled: spec.markSizeAccessor !== undefined, @@ -589,12 +512,15 @@ function renderGeometries( isMixedChart, spec.pointStyleAccessor, ); - indexedGeometryMap.merge(renderedBubbles.indexedGeometryMap); - bubbles.push(renderedBubbles.bubbleGeometry); + geometriesIndex.merge(renderedBubbles.indexedGeometryMap); + bubbles.push({ + panel, + value: renderedBubbles.bubbleGeometry, + }); geometriesCounts.bubblePoints += renderedBubbles.bubbleGeometry.points.length; geometriesCounts.bubbles += 1; } else if (isLineSeriesSpec(spec)) { - const lineShift = clusteredCount > 0 ? clusteredCount : 1; + const lineShift = barIndexOrder && barIndexOrder.length > 0 ? barIndexOrder.length : 1; const lineSeriesStyle = spec.lineSeriesStyle ? mergePartial(chartTheme.lineSeriesStyle, spec.lineSeriesStyle, { mergeOptionalPartialValues: true }) : chartTheme.lineSeriesStyle; @@ -607,6 +533,7 @@ function renderGeometries( ds, xScale, yScale, + panel, color, spec.curve || CurveType.LINEAR, isBandedSpec(spec.y0Accessors), @@ -619,12 +546,16 @@ function renderGeometries( spec.pointStyleAccessor, hasFitFnConfigured(spec.fit), ); - indexedGeometryMap.merge(renderedLines.indexedGeometryMap); - lines.push(renderedLines.lineGeometry); + + geometriesIndex.merge(renderedLines.indexedGeometryMap); + lines.push({ + panel, + value: renderedLines.lineGeometry, + }); geometriesCounts.linePoints += renderedLines.lineGeometry.points.length; geometriesCounts.lines += 1; } else if (isAreaSeriesSpec(spec)) { - const areaShift = clusteredCount > 0 ? clusteredCount : 1; + const areaShift = barIndexOrder && barIndexOrder.length > 0 ? barIndexOrder.length : 1; const areaSeriesStyle = spec.areaSeriesStyle ? mergePartial(chartTheme.areaSeriesStyle, spec.areaSeriesStyle, { mergeOptionalPartialValues: true }) : chartTheme.areaSeriesStyle; @@ -635,6 +566,7 @@ function renderGeometries( ds, xScale, yScale, + panel, color, spec.curve || CurveType.LINEAR, isBandedSpec(spec.y0Accessors), @@ -644,34 +576,38 @@ function renderGeometries( enabled: spec.markSizeAccessor !== undefined, ratio: chartTheme.markSizeRatio, }, - isStacked, + spec.stackAccessors ? spec.stackAccessors.length > 0 : false, spec.pointStyleAccessor, hasFitFnConfigured(spec.fit), - stackMode, ); - indexedGeometryMap.merge(renderedAreas.indexedGeometryMap); - areas.push(renderedAreas.areaGeometry); + geometriesIndex.merge(renderedAreas.indexedGeometryMap); + areas.push({ + panel, + value: renderedAreas.areaGeometry, + }); geometriesCounts.areasPoints += renderedAreas.areaGeometry.points.length; geometriesCounts.areas += 1; } } return { - points, - bars, - areas, - lines, - bubbles, - indexedGeometryMap, + geometries: { + points, + bars, + areas, + lines, + bubbles, + }, + geometriesIndex, geometriesCounts, }; } /** @internal */ -export function computeChartTransform(chartDimensions: Dimensions, chartRotation: Rotation): Transform { +export function computeChartTransform({ width, height }: Size, chartRotation: Rotation): Transform { if (chartRotation === 90) { return { - x: chartDimensions.width, + x: width, y: 0, rotate: 90, }; @@ -679,14 +615,14 @@ export function computeChartTransform(chartDimensions: Dimensions, chartRotation if (chartRotation === -90) { return { x: 0, - y: chartDimensions.height, + y: height, rotate: -90, }; } if (chartRotation === 180) { return { - x: chartDimensions.width, - y: chartDimensions.height, + x: width, + y: height, rotate: 180, }; } @@ -700,3 +636,16 @@ export function computeChartTransform(chartDimensions: Dimensions, chartRotation function hasFitFnConfigured(fit?: Fit | FitConfig) { return Boolean(fit && ((fit as FitConfig).type || fit) !== Fit.None); } + +/** @internal */ +export function getBarIndexKey( + { spec, specId, groupId, yAccessor, splitAccessors }: DataSeries, + histogramModeEnabled: boolean, +) { + const isStacked = isStackedSpec(spec, histogramModeEnabled); + if (isStacked) { + return [groupId, '__stacked__'].join('__-__'); + } + + return [groupId, specId, ...splitAccessors.values(), yAccessor].join('__-__'); +} diff --git a/src/chart_types/xy_chart/tooltip/tooltip.test.ts b/src/chart_types/xy_chart/tooltip/tooltip.test.ts index a743e062c2..e1e2cb0722 100644 --- a/src/chart_types/xy_chart/tooltip/tooltip.test.ts +++ b/src/chart_types/xy_chart/tooltip/tooltip.test.ts @@ -18,12 +18,14 @@ */ import { ChartTypes } from '../..'; +import { MockBarGeometry } from '../../../mocks'; +import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs'; import { ScaleType } from '../../../scales/constants'; import { SpecTypes } from '../../../specs/constants'; import { Position, RecursivePartial } from '../../../utils/commons'; import { BarGeometry } from '../../../utils/geometry'; import { AxisStyle } from '../../../utils/themes/theme'; -import { AxisSpec, BarSeriesSpec, SeriesTypes, TickFormatter } from '../utils/specs'; +import { AxisSpec, BarSeriesSpec, TickFormatter } from '../utils/specs'; import { formatTooltip } from './tooltip'; const style: RecursivePartial = { @@ -36,23 +38,20 @@ const style: RecursivePartial = { describe('Tooltip formatting', () => { const SPEC_ID_1 = 'bar_1'; const SPEC_GROUP_ID_1 = 'bar_group_1'; - const SPEC_1: BarSeriesSpec = { - chartType: ChartTypes.XYAxis, - specType: SpecTypes.Series, + const SPEC_1 = MockSeriesSpec.bar({ id: SPEC_ID_1, groupId: SPEC_GROUP_ID_1, - seriesType: SeriesTypes.Bar, data: [], xAccessor: 0, yAccessors: [1], yScaleType: ScaleType.Linear, xScaleType: ScaleType.Linear, - }; - const bandedSpec = { + }); + const bandedSpec = MockSeriesSpec.bar({ ...SPEC_1, y0Accessors: [1], - }; - const YAXIS_SPEC: AxisSpec = { + }); + const YAXIS_SPEC = MockGlobalSpec.axis({ chartType: ChartTypes.XYAxis, specType: SpecTypes.Axis, id: 'axis_1', @@ -63,7 +62,7 @@ describe('Tooltip formatting', () => { showOverlappingTicks: false, style, tickFormat: jest.fn((d) => `${d}`), - }; + }); const seriesStyle = { rect: { opacity: 1, @@ -81,7 +80,7 @@ describe('Tooltip formatting', () => { padding: 2, }, }; - const indexedGeometry: BarGeometry = { + const indexedGeometry = MockBarGeometry.default({ x: 0, y: 0, width: 0, @@ -102,8 +101,8 @@ describe('Tooltip formatting', () => { datum: { x: 1, y: 10 }, }, seriesStyle, - }; - const indexedBandedGeometry: BarGeometry = { + }); + const indexedBandedGeometry = MockBarGeometry.default({ x: 0, y: 0, width: 0, @@ -124,7 +123,7 @@ describe('Tooltip formatting', () => { datum: { x: 1, y: 10 }, }, seriesStyle, - }; + }); test('format simple tooltip', () => { const tooltipValue = formatTooltip(indexedGeometry, SPEC_1, false, false, false, YAXIS_SPEC); diff --git a/src/chart_types/xy_chart/utils/__snapshots__/series.test.ts.snap b/src/chart_types/xy_chart/utils/__snapshots__/series.test.ts.snap index ba2414c70e..7bb5d77a3a 100644 --- a/src/chart_types/xy_chart/utils/__snapshots__/series.test.ts.snap +++ b/src/chart_types/xy_chart/utils/__snapshots__/series.test.ts.snap @@ -10,101 +10,64 @@ Array [ "y": 1, }, "initialY0": null, - "initialY1": null, + "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, - "y1": null, + "y1": 1, }, - ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{y-1}", - "seriesKeys": Array [ - 1, - "y1", - ], - "specId": "spec1", - "splitAccessors": Map { - "y" => 1, - }, - "yAccessor": "y1", - }, - Object { - "data": Array [ Object { "datum": Object { "x": 1, "y": 2, }, "initialY0": null, - "initialY1": null, + "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, - "y1": null, + "y1": 2, }, - ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{y-2}", - "seriesKeys": Array [ - 2, - "y1", - ], - "specId": "spec1", - "splitAccessors": Map { - "y" => 2, - }, - "yAccessor": "y1", - }, - Object { - "data": Array [ Object { "datum": Object { "x": 2, "y": 10, }, "initialY0": null, - "initialY1": null, + "initialY1": 10, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, - "y1": null, + "y1": 10, }, - ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{y-10}", - "seriesKeys": Array [ - 10, - "y1", - ], - "specId": "spec1", - "splitAccessors": Map { - "y" => 10, - }, - "yAccessor": "y1", - }, - Object { - "data": Array [ Object { "datum": Object { "x": 3, "y": 6, }, "initialY0": null, - "initialY1": null, + "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, - "y1": null, + "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{y-6}", + "key": "groupId{__global__}spec{spec1}yAccessor{y}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ - 6, - "y1", + "y", ], "specId": "spec1", - "splitAccessors": Map { - "y" => 6, - }, - "yAccessor": "y1", + "splitAccessors": Map {}, + "yAccessor": "y", }, ] `; @@ -122,85 +85,113 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, }, Object { "datum": Object { - "g": "b", - "x": 0, + "g": "a", + "x": 1, "y": 2, }, "initialY0": null, "initialY1": 2, "mark": null, - "x": 0, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 1, "y0": null, "y1": 2, }, Object { "datum": Object { "g": "a", - "x": 1, - "y": 2, + "x": 2, + "y": 3, }, "initialY0": null, - "initialY1": 2, + "initialY1": 3, "mark": null, - "x": 1, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 2, "y0": null, - "y1": 2, + "y1": 3, }, Object { "datum": Object { - "g": "b", - "x": 1, - "y": 3, + "g": "a", + "x": 3, + "y": 4, }, "initialY0": null, - "initialY1": 3, + "initialY1": 4, "mark": null, - "x": 1, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 3, "y0": null, - "y1": 3, + "y1": 4, }, + ], + "key": "groupId{__global__}spec{spec1}yAccessor{y}splitAccessors{g-a}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", + "seriesKeys": Array [ + "a", + "y", + ], + "specId": "spec1", + "splitAccessors": Map { + "g" => "a", + }, + "yAccessor": "y", + }, + Object { + "data": Array [ Object { "datum": Object { - "g": "a", - "x": 2, - "y": 3, + "g": "b", + "x": 0, + "y": 2, }, "initialY0": null, - "initialY1": 3, + "initialY1": 2, "mark": null, - "x": 2, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 0, "y0": null, - "y1": 3, + "y1": 2, }, Object { "datum": Object { "g": "b", - "x": 2, - "y": 4, + "x": 1, + "y": 3, }, "initialY0": null, - "initialY1": 4, + "initialY1": 3, "mark": null, - "x": 2, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 1, "y0": null, - "y1": 4, + "y1": 3, }, Object { "datum": Object { - "g": "a", - "x": 3, + "g": "b", + "x": 2, "y": 4, }, "initialY0": null, "initialY1": 4, "mark": null, - "x": 3, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 2, "y0": null, "y1": 4, }, @@ -213,17 +204,22 @@ Array [ "initialY0": null, "initialY1": 5, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 5, }, ], - "key": "spec{spec1}yAccessor{y}splitAccessors{}", + "key": "groupId{__global__}spec{spec1}yAccessor{y}splitAccessors{g-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ + "b", "y", ], "specId": "spec1", - "splitAccessors": Map {}, + "splitAccessors": Map { + "g" => "b", + }, "yAccessor": "y", }, ] @@ -243,6 +239,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -257,6 +255,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -271,6 +271,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 1, @@ -285,12 +287,14 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y}splitAccessors{g1-a|g2-s}", + "key": "groupId{__global__}spec{spec1}yAccessor{y}splitAccessors{g1-a|g2-s}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "a", "s", @@ -315,6 +319,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -329,6 +335,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -343,6 +351,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 2, @@ -357,12 +367,14 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y}splitAccessors{g1-a|g2-p}", + "key": "groupId{__global__}spec{spec1}yAccessor{y}splitAccessors{g1-a|g2-p}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "a", "p", @@ -387,6 +399,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -401,6 +415,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -415,6 +431,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 3, @@ -429,12 +447,14 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y}splitAccessors{g1-b|g2-s}", + "key": "groupId{__global__}spec{spec1}yAccessor{y}splitAccessors{g1-b|g2-s}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "b", "s", @@ -459,6 +479,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -473,6 +495,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -487,6 +511,8 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 4, @@ -501,12 +527,14 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y}splitAccessors{g1-b|g2-p}", + "key": "groupId{__global__}spec{spec1}yAccessor{y}splitAccessors{g1-b|g2-p}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "b", "p", @@ -535,6 +563,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -548,6 +578,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -561,6 +593,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 1, @@ -574,12 +608,14 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "y1", ], @@ -598,6 +634,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 3, @@ -611,6 +649,8 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 7, @@ -624,6 +664,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 2, @@ -637,12 +679,14 @@ Array [ "initialY0": null, "initialY1": 10, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 10, }, ], - "key": "spec{spec1}yAccessor{y2}splitAccessors{}", + "key": "groupId{__global__}spec{spec1}yAccessor{y2}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "y2", ], @@ -667,6 +711,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -681,6 +727,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -695,6 +743,8 @@ Array [ "initialY0": null, "initialY1": 10, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 10, @@ -709,12 +759,14 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 7, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-a}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-a}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "a", "y1", @@ -737,6 +789,8 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 4, @@ -751,6 +805,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 1, @@ -765,6 +821,8 @@ Array [ "initialY0": null, "initialY1": 5, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 5, @@ -779,12 +837,14 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 3, }, ], - "key": "spec{spec1}yAccessor{y2}splitAccessors{g-a}", + "key": "groupId{__global__}spec{spec1}yAccessor{y2}splitAccessors{g-a}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "a", "y2", @@ -807,6 +867,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 3, @@ -821,6 +883,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -835,6 +899,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 3, @@ -849,12 +915,14 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-b}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "b", "y1", @@ -877,6 +945,8 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 6, @@ -891,6 +961,8 @@ Array [ "initialY0": null, "initialY1": 5, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 5, @@ -905,6 +977,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 1, @@ -919,12 +993,14 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y2}splitAccessors{g-b}", + "key": "groupId{__global__}spec{spec1}yAccessor{y2}splitAccessors{g-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "b", "y2", @@ -953,6 +1029,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -968,6 +1046,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -983,6 +1063,8 @@ Array [ "initialY0": null, "initialY1": 10, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 10, @@ -998,6 +1080,8 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 7, @@ -1013,12 +1097,14 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 7, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g1-cdn.google.com|g2-direct-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g1-cdn.google.com|g2-direct-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cdn.google.com", "direct-cdn", @@ -1044,6 +1130,8 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 4, @@ -1059,6 +1147,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 1, @@ -1074,6 +1164,8 @@ Array [ "initialY0": null, "initialY1": 5, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 5, @@ -1089,6 +1181,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 3, @@ -1104,12 +1198,14 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 3, }, ], - "key": "spec{spec1}yAccessor{y2}splitAccessors{g1-cdn.google.com|g2-direct-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y2}splitAccessors{g1-cdn.google.com|g2-direct-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cdn.google.com", "direct-cdn", @@ -1135,6 +1231,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -1150,6 +1248,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -1165,6 +1265,8 @@ Array [ "initialY0": null, "initialY1": 10, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 10, @@ -1180,6 +1282,8 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 7, @@ -1195,12 +1299,14 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 7, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g1-cdn.google.com|g2-indirect-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g1-cdn.google.com|g2-indirect-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cdn.google.com", "indirect-cdn", @@ -1226,6 +1332,8 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 4, @@ -1241,6 +1349,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 1, @@ -1256,6 +1366,8 @@ Array [ "initialY0": null, "initialY1": 5, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 5, @@ -1271,6 +1383,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 3, @@ -1286,12 +1400,14 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 3, }, ], - "key": "spec{spec1}yAccessor{y2}splitAccessors{g1-cdn.google.com|g2-indirect-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y2}splitAccessors{g1-cdn.google.com|g2-indirect-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cdn.google.com", "indirect-cdn", @@ -1317,6 +1433,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 3, @@ -1332,6 +1450,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -1347,6 +1467,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 3, @@ -1362,6 +1484,8 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 6, @@ -1377,12 +1501,14 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g1-cloudflare.com|g2-direct-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g1-cloudflare.com|g2-direct-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cloudflare.com", "direct-cdn", @@ -1408,6 +1534,8 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 6, @@ -1423,6 +1551,8 @@ Array [ "initialY0": null, "initialY1": 5, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 5, @@ -1438,6 +1568,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 1, @@ -1453,6 +1585,8 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 4, @@ -1468,12 +1602,14 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y2}splitAccessors{g1-cloudflare.com|g2-direct-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y2}splitAccessors{g1-cloudflare.com|g2-direct-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cloudflare.com", "direct-cdn", @@ -1499,6 +1635,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 3, @@ -1514,6 +1652,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -1529,6 +1669,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 3, @@ -1544,6 +1686,8 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 6, @@ -1559,12 +1703,14 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g1-cloudflare.com|g2-indirect-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g1-cloudflare.com|g2-indirect-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cloudflare.com", "indirect-cdn", @@ -1590,6 +1736,8 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 6, @@ -1605,6 +1753,8 @@ Array [ "initialY0": null, "initialY1": 5, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 5, @@ -1620,6 +1770,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 1, @@ -1635,6 +1787,8 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 4, @@ -1650,12 +1804,14 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y2}splitAccessors{g1-cloudflare.com|g2-indirect-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y2}splitAccessors{g1-cloudflare.com|g2-indirect-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cloudflare.com", "indirect-cdn", @@ -21866,7 +22022,7 @@ Array [ "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-a}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-a}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "a", "y1", @@ -21936,7 +22092,7 @@ Array [ "y1": 8, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-b}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "b", "y1", @@ -22006,7 +22162,7 @@ Array [ "y1": 12, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-c}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-c}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "c", "y1", @@ -22076,7 +22232,7 @@ Array [ "y1": 16, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-d}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-d}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "d", "y1", @@ -22149,7 +22305,7 @@ Array [ "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-a}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-a}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "a", "y1", @@ -22215,7 +22371,7 @@ Array [ "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-b}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "b", "y1", @@ -22288,7 +22444,7 @@ Array [ "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-a}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-a}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "a", "y1", @@ -22354,7 +22510,7 @@ Array [ "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-b}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "b", "y1", @@ -22430,7 +22586,7 @@ Array [ "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-a}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-a}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "a", "y1", @@ -22504,7 +22660,7 @@ Array [ "y1": 8, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-b}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "b", "y1", @@ -22580,7 +22736,7 @@ Array [ "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-a}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-a}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "a", "y1", @@ -22654,7 +22810,7 @@ Array [ "y1": 8, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-b}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "b", "y1", @@ -22727,7 +22883,7 @@ Array [ "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-a}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-a}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "a", "y1", @@ -22793,7 +22949,7 @@ Array [ "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g-b}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "b", "y1", @@ -22822,6 +22978,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -22837,6 +22995,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -22852,6 +23012,8 @@ Array [ "initialY0": null, "initialY1": 10, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 10, @@ -22867,6 +23029,8 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 7, @@ -22882,12 +23046,14 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 7, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g1-cdn.google.com|g2-direct-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g1-cdn.google.com|g2-direct-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cdn.google.com", "direct-cdn", @@ -22913,6 +23079,8 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 4, @@ -22928,6 +23096,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 1, @@ -22943,6 +23113,8 @@ Array [ "initialY0": null, "initialY1": 5, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 5, @@ -22958,6 +23130,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 3, @@ -22973,12 +23147,14 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 3, }, ], - "key": "spec{spec1}yAccessor{y2}splitAccessors{g1-cdn.google.com|g2-direct-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y2}splitAccessors{g1-cdn.google.com|g2-direct-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cdn.google.com", "direct-cdn", @@ -23004,6 +23180,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -23019,6 +23197,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -23034,6 +23214,8 @@ Array [ "initialY0": null, "initialY1": 10, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 10, @@ -23049,6 +23231,8 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 7, @@ -23064,12 +23248,14 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 7, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g1-cdn.google.com|g2-indirect-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g1-cdn.google.com|g2-indirect-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cdn.google.com", "indirect-cdn", @@ -23095,6 +23281,8 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 4, @@ -23110,6 +23298,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 1, @@ -23125,6 +23315,8 @@ Array [ "initialY0": null, "initialY1": 5, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 5, @@ -23140,6 +23332,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 3, @@ -23155,12 +23349,14 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 3, }, ], - "key": "spec{spec1}yAccessor{y2}splitAccessors{g1-cdn.google.com|g2-indirect-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y2}splitAccessors{g1-cdn.google.com|g2-indirect-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cdn.google.com", "indirect-cdn", @@ -23186,6 +23382,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 3, @@ -23201,6 +23399,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -23216,6 +23416,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 3, @@ -23231,6 +23433,8 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 6, @@ -23246,12 +23450,14 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g1-cloudflare.com|g2-direct-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g1-cloudflare.com|g2-direct-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cloudflare.com", "direct-cdn", @@ -23277,6 +23483,8 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 6, @@ -23292,6 +23500,8 @@ Array [ "initialY0": null, "initialY1": 5, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 5, @@ -23307,6 +23517,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 1, @@ -23322,6 +23534,8 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 4, @@ -23337,12 +23551,14 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y2}splitAccessors{g1-cloudflare.com|g2-direct-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y2}splitAccessors{g1-cloudflare.com|g2-direct-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cloudflare.com", "direct-cdn", @@ -23368,6 +23584,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 3, @@ -23383,6 +23601,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -23398,6 +23618,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 3, @@ -23413,6 +23635,8 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 6, @@ -23428,12 +23652,14 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{g1-cloudflare.com|g2-indirect-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{g1-cloudflare.com|g2-indirect-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cloudflare.com", "indirect-cdn", @@ -23459,6 +23685,8 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 6, @@ -23474,6 +23702,8 @@ Array [ "initialY0": null, "initialY1": 5, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 5, @@ -23489,6 +23719,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 1, @@ -23504,6 +23736,8 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 4, @@ -23519,12 +23753,14 @@ Array [ "initialY0": null, "initialY1": 4, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 6, "y0": null, "y1": 4, }, ], - "key": "spec{spec1}yAccessor{y2}splitAccessors{g1-cloudflare.com|g2-indirect-cdn}", + "key": "groupId{__global__}spec{spec1}yAccessor{y2}splitAccessors{g1-cloudflare.com|g2-indirect-cdn}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "cloudflare.com", "indirect-cdn", @@ -23555,6 +23791,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 1, @@ -23570,6 +23808,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 1, @@ -23585,6 +23825,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 3, @@ -23600,6 +23842,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 3, @@ -23615,6 +23859,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 2, @@ -23630,6 +23876,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 2, @@ -23645,6 +23893,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 2, @@ -23660,6 +23910,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 2, @@ -23675,6 +23927,8 @@ Array [ "initialY0": null, "initialY1": 10, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 10, @@ -23690,6 +23944,8 @@ Array [ "initialY0": null, "initialY1": 10, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 10, @@ -23705,6 +23961,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 3, @@ -23720,6 +23978,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 3, @@ -23735,6 +23995,8 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 7, @@ -23750,6 +24012,8 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 7, @@ -23765,6 +24029,8 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 6, @@ -23780,6 +24046,8 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 6, @@ -23795,6 +24063,8 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 7, @@ -23810,6 +24080,8 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 7, @@ -23825,6 +24097,8 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 6, @@ -23840,12 +24114,14 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": "_all", "y0": null, "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y1}splitAccessors{}", + "key": "groupId{__global__}spec{spec1}yAccessor{y1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "y1", ], @@ -23869,6 +24145,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -23882,6 +24160,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 1, @@ -23895,12 +24175,14 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 1, }, ], - "key": "spec{spec1}yAccessor{1}splitAccessors{2-a}", + "key": "groupId{__global__}spec{spec1}yAccessor{1}splitAccessors{2-a}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "a", 1, @@ -23922,6 +24204,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -23935,6 +24219,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 1, @@ -23948,12 +24234,14 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 1, }, ], - "key": "spec{spec1}yAccessor{1}splitAccessors{2-b}", + "key": "groupId{__global__}spec{spec1}yAccessor{1}splitAccessors{2-b}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "b", 1, @@ -23970,150 +24258,205 @@ Array [ exports[`Series should compute data series for stacked specs 1`] = ` Array [ Object { - "counts": Object { - "area": 0, - "bar": 0, - "bubble": 0, - "line": 2, - }, - "dataSeries": Array [ - Object { - "data": Array [ - Object { - "datum": Object { - "x": 0, - "y1": 1, - "y2": 3, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 1, - "mark": null, - "x": 0, - "y0": 0, - "y1": 1, - }, - Object { - "datum": Object { - "x": 1, - "y1": 2, - "y2": 7, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 2, - "mark": null, - "x": 1, - "y0": 0, - "y1": 2, - }, - Object { - "datum": Object { - "x": 2, - "y1": 1, - "y2": 2, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 1, - "mark": null, - "x": 2, - "y0": 0, - "y1": 1, - }, - Object { - "datum": Object { - "x": 3, - "y1": 6, - "y2": 10, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 6, - "mark": null, - "x": 3, - "y0": 0, - "y1": 6, - }, - ], - "key": "spec{spec2}yAccessor{y1}splitAccessors{}", - "seriesKeys": Array [ - "y1", - ], - "specId": "spec2", - "splitAccessors": Map {}, - "yAccessor": "y1", - }, - Object { - "data": Array [ - Object { - "datum": Object { - "x": 0, - "y1": 1, - "y2": 3, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 3, - "mark": null, - "x": 0, - "y0": 1, - "y1": 4, - }, - Object { - "datum": Object { - "x": 1, - "y1": 2, - "y2": 7, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 7, - "mark": null, - "x": 1, - "y0": 2, - "y1": 9, - }, - Object { - "datum": Object { - "x": 2, - "y1": 1, - "y2": 2, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 2, - "mark": null, - "x": 2, - "y0": 1, - "y1": 3, - }, - Object { - "datum": Object { - "x": 3, - "y1": 6, - "y2": 10, - }, - "filled": undefined, - "initialY0": null, - "initialY1": 10, - "mark": null, - "x": 3, - "y0": 6, - "y1": 16, - }, - ], - "key": "spec{spec2}yAccessor{y2}splitAccessors{}", - "seriesKeys": Array [ - "y2", - ], - "specId": "spec2", - "splitAccessors": Map {}, - "yAccessor": "y2", + "data": Array [ + Object { + "datum": Object { + "x": 0, + "y1": 1, + "y2": 3, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 1, + "mark": null, + "x": 0, + "y0": 0, + "y1": 1, + }, + Object { + "datum": Object { + "x": 1, + "y1": 2, + "y2": 7, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 2, + "mark": null, + "x": 1, + "y0": 0, + "y1": 2, + }, + Object { + "datum": Object { + "x": 2, + "y1": 1, + "y2": 2, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 1, + "mark": null, + "x": 2, + "y0": 0, + "y1": 1, + }, + Object { + "datum": Object { + "x": 3, + "y1": 6, + "y2": 10, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 6, + "mark": null, + "x": 3, + "y0": 0, + "y1": 6, }, ], - "groupId": "group2", - "stackMode": undefined, + "key": "groupId{group2}spec{spec2}yAccessor{y1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", + "seriesKeys": Array [ + "y1", + ], + "specId": "spec2", + "splitAccessors": Map {}, + "yAccessor": "y1", + }, + Object { + "data": Array [ + Object { + "datum": Object { + "x": 0, + "y1": 1, + "y2": 3, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 3, + "mark": null, + "x": 0, + "y0": 1, + "y1": 4, + }, + Object { + "datum": Object { + "x": 1, + "y1": 2, + "y2": 7, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 7, + "mark": null, + "x": 1, + "y0": 2, + "y1": 9, + }, + Object { + "datum": Object { + "x": 2, + "y1": 1, + "y2": 2, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 2, + "mark": null, + "x": 2, + "y0": 1, + "y1": 3, + }, + Object { + "datum": Object { + "x": 3, + "y1": 6, + "y2": 10, + }, + "filled": undefined, + "initialY0": null, + "initialY1": 10, + "mark": null, + "x": 3, + "y0": 6, + "y1": 16, + }, + ], + "key": "groupId{group2}spec{spec2}yAccessor{y2}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", + "seriesKeys": Array [ + "y2", + ], + "specId": "spec2", + "splitAccessors": Map {}, + "yAccessor": "y2", + }, + Object { + "data": Array [ + Object { + "datum": Object { + "x": 0, + "y": 1, + }, + "initialY0": null, + "initialY1": 1, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 0, + "y0": null, + "y1": 1, + }, + Object { + "datum": Object { + "x": 1, + "y": 2, + }, + "initialY0": null, + "initialY1": 2, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 1, + "y0": null, + "y1": 2, + }, + Object { + "datum": Object { + "x": 2, + "y": 10, + }, + "initialY0": null, + "initialY1": 10, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 2, + "y0": null, + "y1": 10, + }, + Object { + "datum": Object { + "x": 3, + "y": 6, + }, + "initialY0": null, + "initialY1": 6, + "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "x": 3, + "y0": null, + "y1": 6, + }, + ], + "key": "groupId{group}spec{spec1}yAccessor{y}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", + "seriesKeys": Array [ + "y", + ], + "specId": "spec1", + "splitAccessors": Map {}, + "yAccessor": "y", }, ] `; @@ -24130,6 +24473,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -24142,6 +24487,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -24154,6 +24501,8 @@ Array [ "initialY0": null, "initialY1": 10, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 10, @@ -24166,17 +24515,57 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 6, }, ], - "key": "spec{spec1}yAccessor{y}splitAccessors{}", + "groupId": "group", + "isStacked": false, + "key": "groupId{group}spec{spec1}yAccessor{y}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "y", ], + "seriesType": "line", + "smHorizontalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smVerticalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "spec": Object { + "chartType": "xy_axis", + "data": Array [ + Object { + "x": 0, + "y": 1, + }, + Object { + "x": 1, + "y": 2, + }, + Object { + "x": 2, + "y": 10, + }, + Object { + "x": 3, + "y": 6, + }, + ], + "groupId": "group", + "hideInLegend": false, + "id": "spec1", + "seriesType": "line", + "specType": "series", + "xAccessor": "x", + "xScaleType": "linear", + "yAccessors": Array [ + "y", + ], + "yScaleType": "log", + }, "specId": "spec1", "splitAccessors": Map {}, + "stackMode": undefined, "yAccessor": "y", }, ] @@ -24195,6 +24584,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 1, @@ -24208,6 +24599,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 2, @@ -24221,6 +24614,8 @@ Array [ "initialY0": null, "initialY1": 1, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 1, @@ -24234,17 +24629,65 @@ Array [ "initialY0": null, "initialY1": 6, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 6, }, ], - "key": "spec{spec2}yAccessor{y1}splitAccessors{}", + "groupId": "group2", + "isStacked": true, + "key": "groupId{group2}spec{spec2}yAccessor{y1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "y1", ], + "seriesType": "line", + "smHorizontalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smVerticalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "spec": Object { + "chartType": "xy_axis", + "data": Array [ + Object { + "x": 0, + "y1": 1, + "y2": 3, + }, + Object { + "x": 1, + "y1": 2, + "y2": 7, + }, + Object { + "x": 2, + "y1": 1, + "y2": 2, + }, + Object { + "x": 3, + "y1": 6, + "y2": 10, + }, + ], + "groupId": "group2", + "hideInLegend": false, + "id": "spec2", + "seriesType": "line", + "specType": "series", + "stackAccessors": Array [ + "x", + ], + "xAccessor": "x", + "xScaleType": "linear", + "yAccessors": Array [ + "y1", + "y2", + ], + "yScaleType": "log", + }, "specId": "spec2", "splitAccessors": Map {}, + "stackMode": undefined, "yAccessor": "y1", }, Object { @@ -24258,6 +24701,8 @@ Array [ "initialY0": null, "initialY1": 3, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 0, "y0": null, "y1": 3, @@ -24271,6 +24716,8 @@ Array [ "initialY0": null, "initialY1": 7, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 1, "y0": null, "y1": 7, @@ -24284,6 +24731,8 @@ Array [ "initialY0": null, "initialY1": 2, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 2, "y0": null, "y1": 2, @@ -24297,17 +24746,65 @@ Array [ "initialY0": null, "initialY1": 10, "mark": null, + "smH": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smV": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", "x": 3, "y0": null, "y1": 10, }, ], - "key": "spec{spec2}yAccessor{y2}splitAccessors{}", + "groupId": "group2", + "isStacked": true, + "key": "groupId{group2}spec{spec2}yAccessor{y2}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}", "seriesKeys": Array [ "y2", ], + "seriesType": "line", + "smHorizontalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "smVerticalAccessorValue": "__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__", + "spec": Object { + "chartType": "xy_axis", + "data": Array [ + Object { + "x": 0, + "y1": 1, + "y2": 3, + }, + Object { + "x": 1, + "y1": 2, + "y2": 7, + }, + Object { + "x": 2, + "y1": 1, + "y2": 2, + }, + Object { + "x": 3, + "y1": 6, + "y2": 10, + }, + ], + "groupId": "group2", + "hideInLegend": false, + "id": "spec2", + "seriesType": "line", + "specType": "series", + "stackAccessors": Array [ + "x", + ], + "xAccessor": "x", + "xScaleType": "linear", + "yAccessors": Array [ + "y1", + "y2", + ], + "yScaleType": "log", + }, "specId": "spec2", "splitAccessors": Map {}, + "stackMode": undefined, "yAccessor": "y2", }, ] diff --git a/src/chart_types/xy_chart/utils/axis_utils.test.ts b/src/chart_types/xy_chart/utils/axis_utils.test.ts index 00135cf422..54c3d69dc4 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -21,6 +21,8 @@ import { DateTime } from 'luxon'; import moment from 'moment-timezone'; import { ChartTypes } from '../..'; +import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs/specs'; +import { MockStore } from '../../../mocks/store/store'; import { Scale } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; import { SpecTypes } from '../../../specs/constants'; @@ -32,23 +34,24 @@ import { AxisId, GroupId } from '../../../utils/ids'; import { LIGHT_THEME } from '../../../utils/themes/light_theme'; import { AxisStyle, TextOffset } from '../../../utils/themes/theme'; import { XDomain, YDomain } from '../domains/types'; +import { computeAxesGeometriesSelector } from '../state/selectors/compute_axes_geometries'; +import { computeAxisTicksDimensionsSelector } from '../state/selectors/compute_axis_ticks_dimensions'; +import { getAxesStylesSelector } from '../state/selectors/get_axis_styles'; +import { computeGridLinesSelector } from '../state/selectors/get_grid_lines'; import { mergeYCustomDomainsByGroupId } from '../state/selectors/merge_y_custom_domains'; import { AxisTick, AxisTicksDimensions, - computeAxisGridLinePositions, computeAxisTicksDimensions, computeRotatedLabelDimensions, getAvailableTicks, getAxisPosition, - getAxisTicksPositions, - getHorizontalAxisGridLineProps, + getAxesGeometries, getHorizontalAxisTickLineProps, getMaxLabelDimensions, getMinMaxRange, getScaleForAxisSpec, getTickLabelProps, - getVerticalAxisGridLineProps, getVerticalAxisTickLineProps, getVisibleTicks, isYDomain, @@ -90,7 +93,7 @@ describe('Axis computational utils', () => { () => (SVGElement.prototype.getBoundingClientRect = function() { const text = this.textContent || 0; - return { ...mockedRect, width: Number(text) * 10, heigh: Number(text) * 10 }; + return { ...mockedRect, width: Number(text) * 10, height: Number(text) * 10 }; }), ); afterEach(() => (SVGElement.prototype.getBoundingClientRect = originalGetBBox)); @@ -108,8 +111,9 @@ describe('Axis computational utils', () => { maxLabelBboxHeight: 10, maxLabelTextWidth: 10, maxLabelTextHeight: 10, + isHidden: false, }; - const verticalAxisSpec: AxisSpec = { + const verticalAxisSpec = MockGlobalSpec.axis({ chartType: ChartTypes.XYAxis, specType: SpecTypes.Axis, id: 'axis_1', @@ -121,9 +125,9 @@ describe('Axis computational utils', () => { style, showGridLines: true, integersOnly: false, - }; + }); - const horizontalAxisSpec: AxisSpec = { + const horizontalAxisSpec = MockGlobalSpec.axis({ chartType: ChartTypes.XYAxis, specType: SpecTypes.Axis, id: 'axis_2', @@ -134,9 +138,9 @@ describe('Axis computational utils', () => { position: Position.Top, style, integersOnly: false, - }; + }); - const verticalAxisSpecWTitle: AxisSpec = { + const verticalAxisSpecWTitle = MockGlobalSpec.axis({ chartType: ChartTypes.XYAxis, specType: SpecTypes.Axis, id: 'axis_1', @@ -149,8 +153,8 @@ describe('Axis computational utils', () => { style, showGridLines: true, integersOnly: false, - }; - const xAxisWithTime: AxisSpec = { + }); + const xAxisWithTime = MockGlobalSpec.axis({ chartType: ChartTypes.XYAxis, specType: SpecTypes.Axis, id: 'axis_1', @@ -164,7 +168,7 @@ describe('Axis computational utils', () => { tickFormat: niceTimeFormatter([1551438000000, 1551441300000]), showGridLines: true, integersOnly: false, - }; + }); // const horizontalAxisSpecWTitle: AxisSpec = { // id: ('axis_2'), @@ -179,7 +183,19 @@ describe('Axis computational utils', () => { // return `${value}`; // }, // }; - + const lineSeriesSpec = MockSeriesSpec.line({ + id: 'line', + groupId: 'group_1', + xAccessor: 0, + yAccessors: [1], + xScaleType: ScaleType.Linear, + yScaleType: ScaleType.Linear, + data: [ + [0, 0], + [0.5, 0.5], + [1, 1], + ], + }); const xDomain: XDomain = { type: 'xDomain', scaleType: ScaleType.Linear, @@ -346,6 +362,7 @@ describe('Axis computational utils', () => { maxLabelTextWidth: 100, tickLabels: [], tickValues: [], + isHidden: false, }; const offset: TextOffset = { x: 0, @@ -562,6 +579,7 @@ describe('Axis computational utils', () => { maxLabelBboxHeight: 20, maxLabelTextWidth: 10, maxLabelTextHeight: 20, + isHidden: false, }; const visibleTicks = getVisibleTicks(allTicks, verticalAxisSpec, axis2Dims); const expectedVisibleTicks = [ @@ -597,6 +615,7 @@ describe('Axis computational utils', () => { maxLabelBboxHeight: 20, maxLabelTextWidth: 10, maxLabelTextHeight: 20, + isHidden: false, }; verticalAxisSpec.showOverlappingTicks = true; @@ -639,8 +658,6 @@ describe('Axis computational utils', () => { const minMax = getMinMaxRange(Position.Bottom, 0, { width: 100, height: 50, - top: 0, - left: 0, }); expect(minMax).toEqual({ minRange: 0, maxRange: 100 }); }); @@ -648,8 +665,6 @@ describe('Axis computational utils', () => { const minMax = getMinMaxRange(Position.Bottom, 90, { width: 100, height: 50, - top: 0, - left: 0, }); expect(minMax).toEqual({ minRange: 0, maxRange: 100 }); }); @@ -657,8 +672,6 @@ describe('Axis computational utils', () => { const minMax = getMinMaxRange(Position.Bottom, 180, { width: 100, height: 50, - top: 0, - left: 0, }); expect(minMax).toEqual({ minRange: 100, maxRange: 0 }); }); @@ -666,8 +679,6 @@ describe('Axis computational utils', () => { const minMax = getMinMaxRange(Position.Bottom, -90, { width: 100, height: 50, - top: 0, - left: 0, }); expect(minMax).toEqual({ minRange: 100, maxRange: 0 }); }); @@ -675,8 +686,6 @@ describe('Axis computational utils', () => { const minMax = getMinMaxRange(Position.Left, 90, { width: 100, height: 50, - top: 0, - left: 0, }); expect(minMax).toEqual({ minRange: 0, maxRange: 50 }); }); @@ -684,8 +693,6 @@ describe('Axis computational utils', () => { const minMax = getMinMaxRange(Position.Left, 180, { width: 100, height: 50, - top: 0, - left: 0, }); expect(minMax).toEqual({ minRange: 0, maxRange: 50 }); }); @@ -693,8 +700,6 @@ describe('Axis computational utils', () => { const minMax = getMinMaxRange(Position.Right, -90, { width: 100, height: 50, - top: 0, - left: 0, }); expect(minMax).toEqual({ minRange: 50, maxRange: 0 }); }); @@ -921,7 +926,7 @@ describe('Axis computational utils', () => { const leftAxisTickLinePositions = getVerticalAxisTickLineProps(Position.Left, tickPadding, tickSize, tickPosition); - expect(leftAxisTickLinePositions).toEqual([5, 10, -5, 10]); + expect(leftAxisTickLinePositions).toEqual({ x1: 5, y1: 10, x2: -5, y2: 10 }); const rightAxisTickLinePositions = getVerticalAxisTickLineProps( Position.Right, @@ -930,11 +935,11 @@ describe('Axis computational utils', () => { tickPosition, ); - expect(rightAxisTickLinePositions).toEqual([0, 10, 10, 10]); + expect(rightAxisTickLinePositions).toEqual({ x1: 0, y1: 10, x2: 10, y2: 10 }); const topAxisTickLinePositions = getHorizontalAxisTickLineProps(Position.Top, axisHeight, tickSize, tickPosition); - expect(topAxisTickLinePositions).toEqual([10, 10, 10, 20]); + expect(topAxisTickLinePositions).toEqual({ x1: 10, y1: 10, x2: 10, y2: 20 }); const bottomAxisTickLinePositions = getHorizontalAxisTickLineProps( Position.Bottom, @@ -943,21 +948,7 @@ describe('Axis computational utils', () => { tickPosition, ); - expect(bottomAxisTickLinePositions).toEqual([10, 0, 10, 10]); - }); - - test('should compute axis grid line positions', () => { - const tickPosition = 10; - const chartWidth = 100; - const chartHeight = 200; - - const verticalAxisGridLinePositions = getVerticalAxisGridLineProps(tickPosition, chartWidth); - - expect(verticalAxisGridLinePositions).toEqual([0, 10, 100, 10]); - - const horizontalAxisGridLinePositions = getHorizontalAxisGridLineProps(tickPosition, chartHeight); - - expect(horizontalAxisGridLinePositions).toEqual([10, 0, 10, 200]); + expect(bottomAxisTickLinePositions).toEqual({ x1: 10, y1: 0, x2: 10, y2: 10 }); }); test('should compute axis ticks positions with title', () => { @@ -971,7 +962,7 @@ describe('Axis computational utils', () => { const axisDims = new Map(); axisDims.set(verticalAxisSpecWTitle.id, axis1Dims); - let axisTicksPosition = getAxisTicksPositions( + let axisTicksPosition = getAxesGeometries( { chartDimensions: chartDim, leftMargin: 0, @@ -983,14 +974,18 @@ describe('Axis computational utils', () => { axesStyles, xDomain, [yDomain], + chartDim, 1, false, (v) => `${v}`, ); - expect(axisTicksPosition.axisPositions.get(verticalAxisSpecWTitle.id)).toEqual({ - top: 0, - left: 10, + const verticalAxisGeoms = axisTicksPosition.find(({ axis: { id } }) => id === verticalAxisSpecWTitle.id); + expect(verticalAxisGeoms?.anchorPoint).toEqual({ + y: 0, + x: 10, + }); + expect(verticalAxisGeoms?.size).toEqual({ width: 50, height: 100, }); @@ -999,7 +994,7 @@ describe('Axis computational utils', () => { axisDims.set(verticalAxisSpec.id, axis1Dims); - axisTicksPosition = getAxisTicksPositions( + axisTicksPosition = getAxesGeometries( { chartDimensions: chartDim, leftMargin: 0, @@ -1011,14 +1006,17 @@ describe('Axis computational utils', () => { axesStyles, xDomain, [yDomain], + chartDim, 1, false, (v) => `${v}`, ); - - expect(axisTicksPosition.axisPositions.get(verticalAxisSpecWTitle.id)).toEqual({ - top: 0, - left: 10, + const verticalAxisSpecWTitleGeoms = axisTicksPosition.find(({ axis: { id } }) => id === verticalAxisSpecWTitle.id); + expect(verticalAxisSpecWTitleGeoms?.anchorPoint).toEqual({ + y: 0, + x: 10, + }); + expect(verticalAxisSpecWTitleGeoms?.size).toEqual({ width: 10, height: 100, }); @@ -1199,7 +1197,7 @@ describe('Axis computational utils', () => { const axisDims = new Map(); axisDims.set('not_a_mapped_one', axis1Dims); - const axisTicksPosition = getAxisTicksPositions( + const axisTicksPosition = getAxesGeometries( { chartDimensions: chartDim, leftMargin: 0, @@ -1211,40 +1209,34 @@ describe('Axis computational utils', () => { axisStyles, xDomain, [yDomain], + chartDim, 1, false, (v) => `${v}`, ); - expect(axisTicksPosition.axisPositions.size).toBe(0); - expect(axisTicksPosition.axisTicks.size).toBe(0); - expect(axisTicksPosition.axisGridLinesPositions.size).toBe(0); - expect(axisTicksPosition.axisVisibleTicks.size).toBe(0); + expect(axisTicksPosition).toHaveLength(0); + // expect(axisTicksPosition.axisTicks.size).toBe(0); + // expect(axisTicksPosition.axisGridLinesPositions.size).toBe(0); + // expect(axisTicksPosition.axisVisibleTicks.size).toBe(0); }); test('should compute axis ticks positions', () => { - const chartRotation = 0; - - const axisSpecs = [verticalAxisSpec]; - const axisStyles = new Map(); - const axisDims = new Map(); - axisDims.set(verticalAxisSpec.id, axis1Dims); - - const axisTicksPosition = getAxisTicksPositions( - { - chartDimensions: chartDim, - leftMargin: 0, - }, - LIGHT_THEME, - chartRotation, - axisSpecs, - axisDims, - axisStyles, - xDomain, - [yDomain], - 1, - false, - (v) => `${v}`, + const store = MockStore.default(); + MockStore.addSpecs( + [ + MockGlobalSpec.settingsNoMargins(), + lineSeriesSpec, + MockGlobalSpec.axis({ + ...verticalAxisSpec, + hide: true, + gridLine: { + visible: true, + }, + }), + ], + store, ); + const gridLines = computeGridLinesSelector(store.getState()); const expectedVerticalAxisGridLines = [ [0, 0, 100, 0], @@ -1260,49 +1252,37 @@ describe('Axis computational utils', () => { [0, 100, 100, 100], ]; - expect(axisTicksPosition.axisGridLinesPositions.get(verticalAxisSpec.id)).toEqual(expectedVerticalAxisGridLines); + const [{ lines }] = gridLines[0].lineGroups; - const axisTicksPositionWithTopLegend = getAxisTicksPositions( - { - chartDimensions: chartDim, - leftMargin: 0, - }, - LIGHT_THEME, - chartRotation, - axisSpecs, - axisDims, - axisStyles, - xDomain, - [yDomain], - 1, - false, - (v) => `${v}`, - ); + expect(lines.map(({ x1, y1, x2, y2 }) => [x1, y1, x2, y2])).toEqual(expectedVerticalAxisGridLines); - const expectedPositionWithTopLegend = { - height: 100, - width: 10, - left: 100, - top: 0, - }; - const verticalAxisWithTopLegendPosition = axisTicksPositionWithTopLegend.axisPositions.get(verticalAxisSpec.id); - expect(verticalAxisWithTopLegendPosition).toEqual(expectedPositionWithTopLegend); + const axisTicksPositionWithTopLegend = computeAxesGeometriesSelector(store.getState()); + + const verticalAxisWithTopLegendPosition = axisTicksPositionWithTopLegend.find( + ({ axis: { id } }) => id === verticalAxisSpec.id, + ); + // TODO check the root cause of having with at 10 on previous implementation + expect(verticalAxisWithTopLegendPosition?.size).toEqual({ height: 0, width: 0 }); + expect(verticalAxisWithTopLegendPosition?.anchorPoint).toEqual({ x: 100, y: 0 }); const ungroupedAxisSpec = { ...verticalAxisSpec, groupId: 'foo' }; const invalidSpecs = [ungroupedAxisSpec]; const computeScalelessSpec = () => { - getAxisTicksPositions( + const axisDims = computeAxisTicksDimensionsSelector(store.getState()); + const axisStyles = getAxesStylesSelector(store.getState()); + getAxesGeometries( { chartDimensions: chartDim, leftMargin: 0, }, LIGHT_THEME, - chartRotation, + 0, invalidSpecs, axisDims, axisStyles, xDomain, [yDomain], + chartDim, 1, false, (v) => `${v}`, @@ -1312,14 +1292,6 @@ describe('Axis computational utils', () => { expect(computeScalelessSpec).toThrowError('Cannot compute scale for axis spec axis_1'); }); - test('should compute positions for grid lines', () => { - const verticalAxisGridLines = computeAxisGridLinePositions(true, 25, chartDim); - expect(verticalAxisGridLines).toEqual([0, 25, 100, 25]); - - const horizontalAxisGridLines = computeAxisGridLinePositions(false, 25, chartDim); - expect(horizontalAxisGridLines).toEqual([25, 0, 25, 100]); - }); - test('should determine if axis belongs to yDomain', () => { const verticalY = isYDomain(Position.Left, 0); expect(verticalY).toBe(true); @@ -1690,13 +1662,13 @@ describe('Axis computational utils', () => { describe('Custom formatting', () => { it('should get custom labels for y axis', () => { - const customFotmatter = (v: any) => `${v} custom`; + const customFormatter = (v: any) => `${v} custom`; const axisSpecs = [verticalAxisSpec]; const axesStyles = new Map(); const axisDims = new Map(); axisDims.set(verticalAxisSpec.id, axis1Dims); - const axisTicksPosition = getAxisTicksPositions( + const axisTicksPosition = getAxesGeometries( { chartDimensions: chartDim, leftMargin: 0, @@ -1708,16 +1680,18 @@ describe('Axis computational utils', () => { axesStyles, xDomain, [yDomain], + chartDim, 1, false, - customFotmatter, + customFormatter, ); const expected = axis1Dims.tickValues .slice() .reverse() - .map(customFotmatter); - expect(axisTicksPosition.axisTicks.get(verticalAxisSpec.id)!.map(({ label }) => label)).toEqual(expected); + .map(customFormatter); + const axisPos = axisTicksPosition.find(({ axis: { id } }) => id === verticalAxisSpec.id); + expect(axisPos?.ticks.map(({ label }) => label)).toEqual(expected); }); it('should not use custom formatter with x axis', () => { @@ -1727,7 +1701,7 @@ describe('Axis computational utils', () => { const axisDims = new Map(); axisDims.set(horizontalAxisSpec.id, axis1Dims); - const axisTicksPosition = getAxisTicksPositions( + const axisTicksPosition = getAxesGeometries( { chartDimensions: chartDim, leftMargin: 0, @@ -1739,13 +1713,16 @@ describe('Axis computational utils', () => { axesStyles, xDomain, [yDomain], + chartDim, 1, false, customFotmatter, ); const expected = axis1Dims.tickValues.slice().map(defaultTickFormatter); - expect(axisTicksPosition.axisTicks.get(horizontalAxisSpec.id)!.map(({ label }) => label)).toEqual(expected); + expect( + axisTicksPosition.find(({ axis: { id } }) => id === horizontalAxisSpec.id)!.ticks.map(({ label }) => label), + ).toEqual(expected); }); it('should use custom axis tick formatter to get labels for x axis', () => { @@ -1760,7 +1737,7 @@ describe('Axis computational utils', () => { const axisDims = new Map(); axisDims.set(spec.id, axis1Dims); - const axisTicksPosition = getAxisTicksPositions( + const axisTicksPosition = getAxesGeometries( { chartDimensions: chartDim, leftMargin: 0, @@ -1772,13 +1749,16 @@ describe('Axis computational utils', () => { axesStyles, xDomain, [yDomain], + chartDim, 1, false, customFotmatter, ); const expected = axis1Dims.tickValues.slice().map(customAxisFotmatter); - expect(axisTicksPosition.axisTicks.get(spec.id)!.map(({ label }) => label)).toEqual(expected); + expect(axisTicksPosition.find(({ axis: { id } }) => id === spec.id)!.ticks.map(({ label }) => label)).toEqual( + expected, + ); }); }); }); diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index a294aee923..9b2ef1192c 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -17,6 +17,7 @@ * under the License. */ +import { Line } from '../../../geoms/types'; import { Scale } from '../../../scales'; import { BBox, BBoxCalculator } from '../../../utils/bbox/bbox_calculator'; import { @@ -26,11 +27,11 @@ import { VerticalAlignment, HorizontalAlignment, getPercentageValue, - mergePartial, } from '../../../utils/commons'; -import { Dimensions, Margins, getSimplePadding } from '../../../utils/dimensions'; +import { Dimensions, Margins, getSimplePadding, Size } from '../../../utils/dimensions'; import { AxisId } from '../../../utils/ids'; import { Logger } from '../../../utils/logger'; +import { Point } from '../../../utils/point'; import { AxisStyle, Theme, TextAlignment, TextOffset } from '../../../utils/themes/theme'; import { XDomain, YDomain } from '../domains/types'; import { MIN_STROKE_WIDTH } from '../renderer/canvas/primitives/line'; @@ -39,8 +40,6 @@ import { isVerticalAxis } from './axis_type_utils'; import { computeXScale, computeYScales } from './scales'; import { AxisSpec, TickFormatterOptions, TickFormatter } from './specs'; -export type AxisLinePosition = [number, number, number, number]; - export interface AxisTick { value: number | string; label: string; @@ -54,6 +53,7 @@ export interface AxisTicksDimensions { maxLabelBboxHeight: number; maxLabelTextWidth: number; maxLabelTextHeight: number; + isHidden: boolean; } export interface TickLabelProps { @@ -78,6 +78,11 @@ export const defaultTickFormatter = (tick: any) => `${tick}`; * @param totalBarsInCluster the total number of grouped series * @param bboxCalculator an instance of the boundingbox calculator * @param chartRotation the rotation of the chart + * @param gridLine + * @param tickLabel + * @param fallBackTickFormatter + * @param barsPadding + * @param enableHistogramMode * @internal */ export function computeAxisTicksDimensions( @@ -92,7 +97,10 @@ export function computeAxisTicksDimensions( barsPadding?: number, enableHistogramMode?: boolean, ): AxisTicksDimensions | null { - if (axisSpec.hide && !gridLine.horizontal.visible && !gridLine.vertical.visible) { + const gridLineVisible = isVerticalAxis(axisSpec.position) ? gridLine.vertical.visible : gridLine.horizontal.visible; + + // don't compute anything on this axis if grid is hidden and axis is hidden + if (axisSpec.hide && !gridLineVisible) { return null; } @@ -124,6 +132,7 @@ export function computeAxisTicksDimensions( return { ...dimensions, + isHidden: axisSpec.hide && gridLineVisible, }; } @@ -349,18 +358,20 @@ function getVerticalAlign( /** * Gets the computed x/y coordinates & alignment properties for an axis tick label. * @param isVerticalAxis if the axis is vertical (in contrast to horizontal) - * @param tickSize length of tick line - * @param tickPadding amount of padding between label and tick line * @param tickPosition position of tick relative to axis line origin and other ticks along it * @param position position of where the axis sits relative to the visualization - * @param axisTicksDimensions computed axis dimensions and values (from computeTickDimensions) + * @param axisSize + * @param tickDimensions + * @param showTicks + * @param textOffset + * @param textAlignment * @internal */ export function getTickLabelProps( { tickLine, tickLabel }: AxisStyle, tickPosition: number, position: Position, - axisPosition: Dimensions, + axisSize: Size, tickDimensions: AxisTicksDimensions, showTicks: boolean, textOffset: TextOffset, @@ -379,7 +390,7 @@ export function getTickLabelProps( const textOffsetY = getVerticalTextOffset(maxLabelTextHeight, verticalAlign) + userOffsets.local.y; if (isVerticalAxis(position)) { - const x = isLeftAxis ? axisPosition.width - tickDimension - labelPadding.inner : tickDimension + labelPadding.inner; + const x = isLeftAxis ? axisSize.width - tickDimension - labelPadding.inner : tickDimension + labelPadding.inner; const offsetX = (isLeftAxis ? -1 : 1) * (maxLabelBboxWidth / 2); return { @@ -398,7 +409,7 @@ export function getTickLabelProps( return { x: tickPosition, - y: isAxisTop ? axisPosition.height - tickDimension - labelPadding.inner : tickDimension + labelPadding.inner, + y: isAxisTop ? axisSize.height - tickDimension - labelPadding.inner : tickDimension + labelPadding.inner, offsetX: userOffsets.global.x, offsetY: offsetY + userOffsets.global.y, textOffsetX, @@ -414,13 +425,13 @@ export function getVerticalAxisTickLineProps( axisWidth: number, tickSize: number, tickPosition: number, -): AxisLinePosition { +): Line { const isLeftAxis = position === Position.Left; const y = tickPosition; const x1 = isLeftAxis ? axisWidth : 0; const x2 = isLeftAxis ? axisWidth - tickSize : tickSize; - return [x1, y, x2, y]; + return { x1, y1: y, x2, y2: y }; } /** @internal */ @@ -429,35 +440,24 @@ export function getHorizontalAxisTickLineProps( axisHeight: number, tickSize: number, tickPosition: number, -): AxisLinePosition { +): Line { const isTopAxis = position === Position.Top; const x = tickPosition; const y1 = isTopAxis ? axisHeight - tickSize : 0; const y2 = isTopAxis ? axisHeight : tickSize; - return [x, y1, x, y2]; -} - -/** @internal */ -export function getVerticalAxisGridLineProps(tickPosition: number, chartWidth: number): AxisLinePosition { - return [0, tickPosition, chartWidth, tickPosition]; -} - -/** @internal */ -export function getHorizontalAxisGridLineProps(tickPosition: number, chartHeight: number): AxisLinePosition { - return [tickPosition, 0, tickPosition, chartHeight]; + return { x1: x, y1, x2: x, y2 }; } /** @internal */ export function getMinMaxRange( axisPosition: Position, chartRotation: Rotation, - chartDimensions: Dimensions, + { width, height }: Size, ): { minRange: number; maxRange: number; } { - const { width, height } = chartDimensions; switch (axisPosition) { case Position.Bottom: case Position.Top: @@ -678,8 +678,21 @@ export function shouldShowTicks({ visible, strokeWidth, size }: AxisStyle['tickL return !axisHidden && visible && size > 0 && strokeWidth >= MIN_STROKE_WIDTH; } +export interface AxisGeometry { + anchorPoint: Point; + size: Size; + axis: { + id: AxisId; + position: Position; + title?: string; + }; + dimension: AxisTicksDimensions; + ticks: AxisTick[]; + visibleTicks: AxisTick[]; +} + /** @internal */ -export function getAxisTicksPositions( +export function getAxesGeometries( computedChartDims: { chartDimensions: Dimensions; leftMargin: number; @@ -691,35 +704,94 @@ export function getAxisTicksPositions( axesStyles: Map, xDomain: XDomain, yDomain: YDomain[], + panel: Size, totalGroupsCount: number, enableHistogramMode: boolean, fallBackTickFormatter: TickFormatter, barsPadding?: number, -): { - axisPositions: Map; - axisTicks: Map; - axisVisibleTicks: Map; - axisGridLinesPositions: Map; -} { - const axisPositions: Map = new Map(); - const axisVisibleTicks: Map = new Map(); - const axisTicks: Map = new Map(); - const axisGridLinesPositions: Map = new Map(); +): Array { + const axesGeometries: Array = []; + const { chartDimensions } = computedChartDims; - let cumTopSum = 0; - let cumBottomSum = chartPaddings.bottom; - let cumLeftSum = computedChartDims.leftMargin; - let cumRightSum = chartPaddings.right; + + // compute the anchor point for every axis group + + const anchorPointByAxisGroups = [...axisDimensions.entries()].reduce( + (acc, [axisId, dimension]) => { + const axisSpec = getSpecsById(axisSpecs, axisId); + if (!axisSpec) { + return acc; + } + + const { axisTitle, tickLine, tickLabel } = axesStyles.get(axisId) ?? sharedAxesStyle; + const labelPadding = getSimplePadding(tickLabel.padding); + const showTicks = shouldShowTicks(tickLine, axisSpec.hide); + const axisTitleHeight = axisSpec.title !== undefined ? axisTitle.fontSize : 0; + const tickDimension = showTicks ? tickLine.size + tickLine.padding : 0; + const labelPaddingSum = tickLabel.visible ? labelPadding.inner + labelPadding.outer : 0; + + const { dimensions, topIncrement, bottomIncrement, leftIncrement, rightIncrement } = getAxisPosition( + chartDimensions, + chartMargins, + axisTitleHeight, + axisTitle, + axisSpec, + dimension, + acc.top, + acc.bottom, + acc.left, + acc.right, + labelPaddingSum, + tickDimension, + tickLabel.visible, + ); + const anchor = { + top: acc.top + topIncrement, + bottom: acc.bottom + bottomIncrement, + left: acc.left + leftIncrement, + right: acc.right + rightIncrement, + }; + acc.pos.set(axisId, { + anchor: { + top: acc.top, + left: acc.left, + right: acc.right, + bottom: acc.bottom, + }, + dimensions, + }); + return { + ...anchor, + pos: acc.pos, + }; + }, + { + top: 0, + bottom: chartPaddings.bottom, + left: computedChartDims.leftMargin, + right: chartPaddings.right, + pos: new Map< + AxisId, + { + anchor: { left: number; right: number; top: number; bottom: number }; + dimensions: Dimensions; + } + >(), + }, + ).pos; axisDimensions.forEach((axisDim, id) => { const axisSpec = getSpecsById(axisSpecs, id); - + const anchorPoint = anchorPointByAxisGroups.get(id); // Consider refactoring this so this condition can be tested // Given some of the values that get passed around, maybe re-write as a reduce instead of forEach? - if (!axisSpec) { + if (!axisSpec || !anchorPoint) { return; } - const minMaxRanges = getMinMaxRange(axisSpec.position, chartRotation, chartDimensions); + + const isVertical = isVerticalAxis(axisSpec.position); + + const minMaxRanges = getMinMaxRange(axisSpec.position, chartRotation, panel); const scale = getScaleForAxisSpec( axisSpec, @@ -739,8 +811,7 @@ export function getAxisTicksPositions( const tickFormatOptions = { timeZone: xDomain.timeZone, }; - const { axisTitle, tickLine, tickLabel, gridLine } = axesStyles.get(id) ?? sharedAxesStyle; - const isVertical = isVerticalAxis(axisSpec.position); + // TODO: Find the true cause of the this offset error const rotationOffset = enableHistogramMode && @@ -757,70 +828,31 @@ export function getAxisTicksPositions( rotationOffset, tickFormatOptions, ); - const visibleTicks = getVisibleTicks(allTicks, axisSpec, axisDim); - const axisSpecConfig = axisSpec.gridLine; - const gridLineThemeStyles = isVertical ? gridLine.vertical : gridLine.horizontal; - const gridLineStyles = axisSpecConfig ? mergePartial(gridLineThemeStyles, axisSpecConfig) : gridLineThemeStyles; - - if (axisSpec.showGridLines ?? gridLineStyles.visible) { - const gridLines = visibleTicks.map( - (tick: AxisTick): AxisLinePosition => computeAxisGridLinePositions(isVertical, tick.position, chartDimensions), - ); - axisGridLinesPositions.set(id, gridLines); - } - - const labelPadding = getSimplePadding(tickLabel.padding); - const showTicks = shouldShowTicks(tickLine, axisSpec.hide); - const axisTitleHeight = axisSpec.title !== undefined ? axisTitle.fontSize : 0; - const tickDimension = showTicks ? tickLine.size + tickLine.padding : 0; - const labelPaddingSum = tickLabel.visible ? labelPadding.inner + labelPadding.outer : 0; - - const axisPosition = getAxisPosition( - chartDimensions, - chartMargins, - axisTitleHeight, - axisTitle, - axisSpec, - axisDim, - cumTopSum, - cumBottomSum, - cumLeftSum, - cumRightSum, - labelPaddingSum, - tickDimension, - tickLabel.visible, - ); - - cumTopSum += axisPosition.topIncrement; - cumBottomSum += axisPosition.bottomIncrement; - cumLeftSum += axisPosition.leftIncrement; - cumRightSum += axisPosition.rightIncrement; - axisPositions.set(id, axisPosition.dimensions); - axisVisibleTicks.set(id, visibleTicks); - axisTicks.set(id, allTicks); + const size = axisDim.isHidden + ? { width: 0, height: 0 } + : { + width: isVertical ? anchorPoint.dimensions.width : panel.width, + height: isVertical ? panel.height : anchorPoint.dimensions.height, + }; + axesGeometries.push({ + axis: { + id: axisSpec.id, + position: axisSpec.position, + title: axisSpec.title, + }, + anchorPoint: { + x: anchorPoint.dimensions.left, + y: anchorPoint.dimensions.top, + }, + size, + dimension: axisDim, + ticks: allTicks, + visibleTicks, + }); }); - - return { - axisPositions, - axisTicks, - axisVisibleTicks, - axisGridLinesPositions, - }; -} - -/** @internal */ -export function computeAxisGridLinePositions( - isVerticalAxis: boolean, - tickPosition: number, - chartDimensions: Dimensions, -): AxisLinePosition { - const positions = isVerticalAxis - ? getVerticalAxisGridLineProps(tickPosition, chartDimensions.width) - : getHorizontalAxisGridLineProps(tickPosition, chartDimensions.height); - - return positions; + return axesGeometries; } /** @internal */ diff --git a/src/chart_types/xy_chart/utils/dimensions.test.ts b/src/chart_types/xy_chart/utils/dimensions.test.ts index e6c795395e..11c03c4bc0 100644 --- a/src/chart_types/xy_chart/utils/dimensions.test.ts +++ b/src/chart_types/xy_chart/utils/dimensions.test.ts @@ -59,6 +59,7 @@ describe('Computed chart dimensions', () => { maxLabelBboxHeight: 10, maxLabelTextWidth: 10, maxLabelTextHeight: 10, + isHidden: false, }; const axisLeftSpec: AxisSpec = { chartType: ChartTypes.XYAxis, diff --git a/src/chart_types/xy_chart/utils/dimensions.ts b/src/chart_types/xy_chart/utils/dimensions.ts index 7d4642d3b2..83024c7a27 100644 --- a/src/chart_types/xy_chart/utils/dimensions.ts +++ b/src/chart_types/xy_chart/utils/dimensions.ts @@ -17,12 +17,11 @@ * under the License. */ -import { Position } from '../../../utils/commons'; -import { Dimensions, getSimplePadding } from '../../../utils/dimensions'; +import { Dimensions } from '../../../utils/dimensions'; import { AxisId } from '../../../utils/ids'; import { Theme, AxisStyle } from '../../../utils/themes/theme'; -import { getSpecsById } from '../state/utils/spec'; -import { AxisTicksDimensions, shouldShowTicks } from './axis_utils'; +import { computeAxesSizes } from '../axes/axes_sizes'; +import { AxisTicksDimensions } from './axis_utils'; import { AxisSpec } from './specs'; /** @@ -50,14 +49,16 @@ export interface ChartDimensions { * Compute the chart dimensions. It's computed removing from the parent dimensions * the axis spaces, the legend and any other specified style margin and padding. * @param parentDimensions the parent dimension - * @param chartTheme the theme style of the chart + * @param theme * @param axisDimensions the axis dimensions + * @param axesStyles * @param axisSpecs the axis specs + * @param legendSizing * @internal */ export function computeChartDimensions( parentDimensions: Dimensions, - { chartMargins, chartPaddings, axes: sharedAxesStyles }: Theme, + theme: Theme, axisDimensions: Map, axesStyles: Map, axisSpecs: AxisSpec[], @@ -82,64 +83,16 @@ export function computeChartDimensions( }; } - let vLeftAxisSpecWidth = 0; - let vRightAxisSpecWidth = 0; - let hTopAxisSpecHeight = 0; - let hBottomAxisSpecHeight = 0; - let horizontalEdgeLabelOverflow = 0; - let verticalEdgeLabelOverflow = 0; - axisDimensions.forEach(({ maxLabelBboxWidth = 0, maxLabelBboxHeight = 0 }, id) => { - const axisSpec = getSpecsById(axisSpecs, id); - if (!axisSpec || axisSpec.hide) { - return; - } - const { tickLine, axisTitle, tickLabel } = axesStyles.get(id) ?? sharedAxesStyles; - const showTicks = shouldShowTicks(tickLine, axisSpec.hide); - const { position, title } = axisSpec; - const titlePadding = getSimplePadding(axisTitle.padding); - const labelPadding = getSimplePadding(tickLabel.padding); - const labelPaddingSum = tickLabel.visible ? labelPadding.inner + labelPadding.outer : 0; + const axisSizes = computeAxesSizes(theme, axisDimensions, axesStyles, axisSpecs); - const tickDimension = showTicks ? tickLine.size + tickLine.padding : 0; - const titleHeight = - title !== undefined && axisTitle.visible ? axisTitle.fontSize + titlePadding.outer + titlePadding.inner : 0; - const axisDimension = labelPaddingSum + tickDimension + titleHeight; - const maxAxisHeight = tickLabel.visible ? maxLabelBboxHeight + axisDimension : axisDimension; - const maxAxisWidth = tickLabel.visible ? maxLabelBboxWidth + axisDimension : axisDimension; - switch (position) { - case Position.Top: - hTopAxisSpecHeight += maxAxisHeight + chartMargins.top; - // find the max half label size to accomodate the left/right labels - horizontalEdgeLabelOverflow = Math.max(horizontalEdgeLabelOverflow, maxLabelBboxWidth / 2); - break; - case Position.Bottom: - hBottomAxisSpecHeight += maxAxisHeight + chartMargins.bottom; - // find the max half label size to accomodate the left/right labels - horizontalEdgeLabelOverflow = Math.max(horizontalEdgeLabelOverflow, maxLabelBboxWidth / 2); - break; - case Position.Right: - vRightAxisSpecWidth += maxAxisWidth + chartMargins.right; - verticalEdgeLabelOverflow = Math.max(verticalEdgeLabelOverflow, maxLabelBboxHeight / 2); - break; - case Position.Left: - default: - vLeftAxisSpecWidth += maxAxisWidth + chartMargins.left; - verticalEdgeLabelOverflow = Math.max(verticalEdgeLabelOverflow, maxLabelBboxHeight / 2); - } - }); - const chartLeftAxisMaxWidth = Math.max(vLeftAxisSpecWidth, horizontalEdgeLabelOverflow + chartMargins.left); - const chartRightAxisMaxWidth = Math.max(vRightAxisSpecWidth, horizontalEdgeLabelOverflow + chartMargins.right); - const chartTopAxisMaxHeight = Math.max(hTopAxisSpecHeight, verticalEdgeLabelOverflow + chartMargins.top); - const chartBottomAxisMaxHeight = Math.max(hBottomAxisSpecHeight, verticalEdgeLabelOverflow + chartMargins.bottom); - - const chartWidth = parentDimensions.width - chartLeftAxisMaxWidth - chartRightAxisMaxWidth; - const chartHeight = parentDimensions.height - chartTopAxisMaxHeight - chartBottomAxisMaxHeight; - - const top = chartTopAxisMaxHeight + chartPaddings.top; - const left = chartLeftAxisMaxWidth + chartPaddings.left; + const chartWidth = parentDimensions.width - axisSizes.left - axisSizes.right; + const chartHeight = parentDimensions.height - axisSizes.top - axisSizes.bottom; + const { chartPaddings } = theme; + const top = axisSizes.top + chartPaddings.top; + const left = axisSizes.left + chartPaddings.left; return { - leftMargin: chartLeftAxisMaxWidth - vLeftAxisSpecWidth, + leftMargin: axisSizes.margin.left, chartDimensions: { top, left, diff --git a/src/chart_types/xy_chart/utils/fill_series.ts b/src/chart_types/xy_chart/utils/fill_series.ts index dd8f318c4d..9553ac7288 100644 --- a/src/chart_types/xy_chart/utils/fill_series.ts +++ b/src/chart_types/xy_chart/utils/fill_series.ts @@ -17,85 +17,59 @@ * under the License. */ import { ScaleType } from '../../../scales/constants'; -import { SpecId, GroupId } from '../../../utils/ids'; -import { YBasicSeriesSpec } from '../domains/y_domain'; -import { getSpecsById } from '../state/utils/spec'; import { DataSeries } from './series'; -import { SeriesSpecs, StackMode, BasicSeriesSpec, isLineSeriesSpec, isAreaSeriesSpec } from './specs'; +import { BasicSeriesSpec, isLineSeriesSpec, isAreaSeriesSpec } from './specs'; /** - * Fill missing x values in all data series * @internal */ export function fillSeries( - series: Map, + dataSeries: DataSeries[], xValues: Set, - seriesSpecs: SeriesSpecs, groupScaleType: ScaleType, - specsByGroupIds: Map< - GroupId, - { - stackMode: StackMode | undefined; - stacked: YBasicSeriesSpec[]; - nonStacked: YBasicSeriesSpec[]; - } - >, -): Map { +): DataSeries[] { const sortedXValues = [...xValues.values()]; - const filledSeries: Map = new Map(); - series.forEach((dataSeries, key) => { - const spec = getSpecsById(seriesSpecs, key); - if (!spec) { - return; + return dataSeries.map((series) => { + const { spec, data, isStacked } = series; + + const noFillRequired = isXFillNotRequired(spec, groupScaleType, isStacked); + if (data.length === xValues.size || noFillRequired) { + return { + ...series, + data, + }; } - const group = specsByGroupIds.get(spec.groupId); - if (!group) { - return; + const filledData: typeof data = []; + const missingValues = new Set(xValues); + for (let i = 0; i < data.length; i++) { + const { x } = data[i]; + filledData.push(data[i]); + missingValues.delete(x); } - const isStacked = Boolean(group.stacked.find(({ id }) => id === key)); - const noFillRequired = isXFillNotRequired(spec, groupScaleType, isStacked); - - const filledDataSeries = dataSeries.map(({ data, ...rest }) => { - if (data.length === xValues.size || noFillRequired) { - return { - ...rest, - data, - }; - } - const filledData: typeof data = []; - const missingValues = new Set(xValues); - for (let i = 0; i < data.length; i++) { - const { x } = data[i]; - filledData.push(data[i]); - missingValues.delete(x); - } - const missingValuesArray = [...missingValues.values()]; - for (let i = 0; i < missingValuesArray.length; i++) { - const missingValue = missingValuesArray[i]; - const index = sortedXValues.indexOf(missingValue); + const missingValuesArray = [...missingValues.values()]; + for (let i = 0; i < missingValuesArray.length; i++) { + const missingValue = missingValuesArray[i]; + const index = sortedXValues.indexOf(missingValue); - filledData.splice(index, 0, { + filledData.splice(index, 0, { + x: missingValue, + y1: null, + y0: null, + initialY1: null, + initialY0: null, + mark: null, + datum: undefined, + filled: { x: missingValue, - y1: null, - y0: null, - initialY1: null, - initialY0: null, - mark: null, - datum: undefined, - filled: { - x: missingValue, - }, - }); - } - return { - ...rest, - data: filledData, - }; - }); - filledSeries.set(key, filledDataSeries); + }, + }); + } + return { + ...series, + data: filledData, + }; }); - return filledSeries; } function isXFillNotRequired(spec: BasicSeriesSpec, groupScaleType: ScaleType, isStacked: boolean) { diff --git a/src/chart_types/xy_chart/utils/fit_function_utils.ts b/src/chart_types/xy_chart/utils/fit_function_utils.ts index d17dba29b8..5416f5f6d2 100644 --- a/src/chart_types/xy_chart/utils/fit_function_utils.ts +++ b/src/chart_types/xy_chart/utils/fit_function_utils.ts @@ -25,14 +25,11 @@ import { isAreaSeriesSpec, isLineSeriesSpec, SeriesSpecs, BasicSeriesSpec } from /** @internal */ export const applyFitFunctionToDataSeries = ( - dataseries: DataSeries[], + dataSeries: DataSeries[], seriesSpecs: SeriesSpecs, xScaleType: ScaleType, ): DataSeries[] => { - const len = dataseries.length; - const formattedValues: DataSeries[] = []; - for (let i = 0; i < len; i++) { - const { specId, data, ...rest } = dataseries[i]; + return dataSeries.map(({ specId, data, ...rest }) => { const spec = getSpecsById(seriesSpecs, specId); if ( @@ -43,14 +40,12 @@ export const applyFitFunctionToDataSeries = ( ) { const fittedData = fitFunction(data, spec.fit, xScaleType); - formattedValues.push({ + return { specId, ...rest, data: fittedData, - }); - } else { - formattedValues.push({ specId, data, ...rest }); + }; } - } - return formattedValues; + return { specId, data, ...rest }; + }); }; diff --git a/src/chart_types/xy_chart/utils/grid_lines.test.ts b/src/chart_types/xy_chart/utils/grid_lines.test.ts new file mode 100644 index 0000000000..96e236d0e5 --- /dev/null +++ b/src/chart_types/xy_chart/utils/grid_lines.test.ts @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { getGridLineForHorizontalAxisAt, getGridLineForVerticalAxisAt } from './grid_lines'; + +describe('Grid lines', () => { + test('should compute positions for grid lines', () => { + const tickPosition = 25; + const panel = { + width: 100, + height: 100, + top: 0, + left: 0, + }; + const verticalAxisGridLines = getGridLineForVerticalAxisAt(tickPosition, panel); + expect(verticalAxisGridLines).toEqual({ x1: 0, y1: 25, x2: 100, y2: 25 }); + + const horizontalAxisGridLines = getGridLineForHorizontalAxisAt(tickPosition, panel); + expect(horizontalAxisGridLines).toEqual({ x1: 25, y1: 0, x2: 25, y2: 100 }); + }); + + test('should compute axis grid line positions', () => { + const panel = { + width: 100, + height: 200, + top: 0, + left: 0, + }; + const tickPosition = 10; + + const verticalAxisGridLinePositions = getGridLineForVerticalAxisAt(tickPosition, panel); + + expect(verticalAxisGridLinePositions).toEqual({ x1: 0, y1: 10, x2: 100, y2: 10 }); + + const horizontalAxisGridLinePositions = getGridLineForHorizontalAxisAt(tickPosition, panel); + + expect(horizontalAxisGridLinePositions).toEqual({ x1: 10, y1: 0, x2: 10, y2: 200 }); + }); +}); diff --git a/src/chart_types/xy_chart/utils/grid_lines.ts b/src/chart_types/xy_chart/utils/grid_lines.ts new file mode 100644 index 0000000000..e057600207 --- /dev/null +++ b/src/chart_types/xy_chart/utils/grid_lines.ts @@ -0,0 +1,150 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Line, Stroke } from '../../../geoms/types'; +import { mergePartial, RecursivePartial } from '../../../utils/commons'; +import { Size } from '../../../utils/dimensions'; +import { AxisId } from '../../../utils/ids'; +import { Point } from '../../../utils/point'; +import { AxisStyle } from '../../../utils/themes/theme'; +import { stringToRGB } from '../../partition_chart/layout/utils/color_library_wrappers'; +import { MIN_STROKE_WIDTH } from '../renderer/canvas/primitives/line'; +import { SmallMultipleScales } from '../state/selectors/compute_small_multiple_scales'; +import { isVerticalAxis } from './axis_type_utils'; +import { AxisGeometry, AxisTick } from './axis_utils'; +import { getPanelSize } from './panel'; +import { getPerPanelMap } from './panel_utils'; +import { AxisSpec } from './specs'; + +/** @internal */ +export interface GridLineGroup { + lines: Array; + stroke: Stroke; + axisId: AxisId; +} + +/** @internal */ +export type LinesGrid = { + panelAnchor: Point; + lineGroups: Array; +}; + +/** @internal */ +export function getGridLines( + axesSpecs: Array, + axesGeoms: Array, + themeAxisStyle: AxisStyle, + scales: SmallMultipleScales, +): Array { + const panelSize = getPanelSize(scales); + return getPerPanelMap(scales, () => { + // get grids per panel (depends on all the axis that exist + const lines = axesGeoms.reduce>((linesAcc, { axis, visibleTicks }) => { + const axisSpec = axesSpecs.find(({ id }) => id === axis.id); + if (!axisSpec) { + return linesAcc; + } + const linesForSpec = getGridLinesForSpec(axisSpec, visibleTicks, themeAxisStyle, panelSize); + if (!linesForSpec) { + return linesAcc; + } + return [...linesAcc, linesForSpec]; + }, []); + return { lineGroups: lines }; + }); +} + +/** + * Get grid lines for a specific axis + * @internal + * @param axisSpec + * @param visibleTicks + * @param themeAxisStyle + * @param panelSize + */ +export function getGridLinesForSpec( + axisSpec: AxisSpec, + visibleTicks: AxisTick[], + themeAxisStyle: AxisStyle, + panelSize: Size, +): GridLineGroup | null { + // vertical ==> horizontal grid lines + const isVertical = isVerticalAxis(axisSpec.position); + + // merge the axis configured style with the theme style + const axisStyle = mergePartial(themeAxisStyle, axisSpec.style as RecursivePartial, { + mergeOptionalPartialValues: true, + }); + const gridLineThemeStyle = isVertical ? axisStyle.gridLine.vertical : axisStyle.gridLine.horizontal; + + // axis can have a configured grid line style + const gridLineStyles = axisSpec.gridLine ? mergePartial(gridLineThemeStyle, axisSpec.gridLine) : gridLineThemeStyle; + + const showGridLines = axisSpec.showGridLines ?? gridLineStyles.visible; + if (!showGridLines) { + return null; + } + + // compute all the lines points for the specific grid + const lines = visibleTicks.map((tick: AxisTick) => { + return isVertical + ? getGridLineForVerticalAxisAt(tick.position, panelSize) + : getGridLineForHorizontalAxisAt(tick.position, panelSize); + }); + + // define the stroke for the specific set of grid lines + if (!gridLineStyles.stroke || !gridLineStyles.strokeWidth || gridLineStyles.strokeWidth < MIN_STROKE_WIDTH) { + return null; + } + const strokeColor = stringToRGB(gridLineStyles.stroke); + strokeColor.opacity = + gridLineStyles.opacity !== undefined ? strokeColor.opacity * gridLineStyles.opacity : strokeColor.opacity; + const stroke: Stroke = { + color: strokeColor, + width: gridLineStyles.strokeWidth, + dash: gridLineStyles.dash, + }; + + return { + lines, + stroke, + axisId: axisSpec.id, + }; +} + +/** + * Get a horizontal grid line at `tickPosition` + * used for vertical axis specs + * @param tickPosition the position of the tick + * @param panelSize the size of the target panel + * @internal + */ +export function getGridLineForVerticalAxisAt(tickPosition: number, panelSize: Size): Line { + return { x1: 0, y1: tickPosition, x2: panelSize.width, y2: tickPosition }; +} + +/** + * Get a vertical grid line at `tickPosition` + * used for horizontal axis specs + * @param tickPosition the position of the tick + * @param panelSize the size of the target panel + * @internal + */ +export function getGridLineForHorizontalAxisAt(tickPosition: number, panelSize: Size): Line { + return { x1: tickPosition, y1: 0, x2: tickPosition, y2: panelSize.height }; +} diff --git a/src/chart_types/xy_chart/utils/group_data_series.ts b/src/chart_types/xy_chart/utils/group_data_series.ts new file mode 100644 index 0000000000..59c39e09cd --- /dev/null +++ b/src/chart_types/xy_chart/utils/group_data_series.ts @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +type Group = Record; +type GroupByKeyFn = (data: T) => string; +type GroupKeysOrKeyFn = Array | GroupByKeyFn; + +export function groupBy(data: T[], keysOrKeyFn: GroupKeysOrKeyFn, asArray: false): Group; +export function groupBy(data: T[], keysOrKeyFn: GroupKeysOrKeyFn, asArray: true): T[][]; +export function groupBy(data: T[], keysOrKeyFn: GroupKeysOrKeyFn, asArray: boolean): T[][] | Group { + const keyFn = Array.isArray(keysOrKeyFn) ? getUniqueKey(keysOrKeyFn) : keysOrKeyFn; + const grouped = data.reduce>((acc, curr) => { + const key = keyFn(curr); + if (!acc[key]) { + acc[key] = []; + } + acc[key].push(curr); + return acc; + }, {}); + return asArray ? Object.values(grouped) : grouped; +} + +export function getUniqueKey(keys: Array, concat = '|') { + return (data: T): string => { + return keys + .map((key) => { + return data[key]; + }) + .join(concat); + }; +} diff --git a/src/chart_types/xy_chart/utils/indexed_geometry_map.ts b/src/chart_types/xy_chart/utils/indexed_geometry_map.ts index 8cbdb03cb9..5b83c46548 100644 --- a/src/chart_types/xy_chart/utils/indexed_geometry_map.ts +++ b/src/chart_types/xy_chart/utils/indexed_geometry_map.ts @@ -22,6 +22,7 @@ import { $Values } from 'utility-types'; import { Bounds } from '../../../utils/d3-delaunay'; import { IndexedGeometry, isPointGeometry } from '../../../utils/geometry'; import { Point } from '../../../utils/point'; +import { PrimitiveValue } from '../../partition_chart/layout/utils/group_by_rollup'; import { IndexedGeometryLinearMap } from './indexed_geometry_linear_map'; import { IndexedGeometrySpatialMap } from './indexed_geometry_spatial_map'; @@ -64,14 +65,25 @@ export class IndexedGeometryMap { } } - find(x: number | string | null, point?: Point): IndexedGeometry[] { + find( + x: number | string | null, + point?: Point, + smHorizontalValue?: PrimitiveValue, + smVerticalValue?: PrimitiveValue, + ): IndexedGeometry[] { if (x === null && !point) { return []; } const spatialValues = point === undefined ? [] : this.spatialMap.find(point); - return [...this.linearMap.find(x), ...spatialValues]; + const values = [...this.linearMap.find(x), ...spatialValues]; + if (!smHorizontalValue || !smVerticalValue) { + return values; + } + return values.filter(({ seriesIdentifier: { smHorizontalAccessorValue, smVerticalAccessorValue } }) => { + return smVerticalAccessorValue === smVerticalValue && smHorizontalAccessorValue === smHorizontalValue; + }); } getMergeData() { diff --git a/src/chart_types/xy_chart/utils/interactions.test.ts b/src/chart_types/xy_chart/utils/interactions.test.ts index e7753a476f..58096fbe34 100644 --- a/src/chart_types/xy_chart/utils/interactions.test.ts +++ b/src/chart_types/xy_chart/utils/interactions.test.ts @@ -17,10 +17,10 @@ * under the License. */ +import { MockBarGeometry, MockPointGeometry } from '../../../mocks'; import { isCrosshairTooltipType, isFollowTooltipType } from '../../../specs'; import { TooltipType } from '../../../specs/constants'; import { Dimensions } from '../../../utils/dimensions'; -import { IndexedGeometry, PointGeometry } from '../../../utils/geometry'; import { areIndexedGeometryArraysEquals, areIndexedGeomsEquals, @@ -46,7 +46,7 @@ const seriesStyle = { }, }; -const ig1: IndexedGeometry = { +const ig1 = MockBarGeometry.default({ color: 'red', seriesIdentifier: { specId: 'ig1', @@ -67,8 +67,8 @@ const ig1: IndexedGeometry = { width: 50, height: 50, seriesStyle, -}; -const ig2: IndexedGeometry = { +}); +const ig2 = MockBarGeometry.default({ seriesIdentifier: { specId: 'ig1', key: '', @@ -89,8 +89,8 @@ const ig2: IndexedGeometry = { width: 10, height: 10, seriesStyle, -}; -const ig3: IndexedGeometry = { +}); +const ig3 = MockBarGeometry.default({ seriesIdentifier: { specId: 'ig1', key: '', @@ -112,8 +112,8 @@ const ig3: IndexedGeometry = { width: 50, height: 50, seriesStyle, -}; -const ig4: IndexedGeometry = { +}); +const ig4 = MockBarGeometry.default({ seriesIdentifier: { specId: 'ig4', key: '', @@ -134,8 +134,8 @@ const ig4: IndexedGeometry = { width: 50, height: 50, seriesStyle, -}; -const ig5: IndexedGeometry = { +}); +const ig5 = MockBarGeometry.default({ seriesIdentifier: { specId: 'ig5', key: '', @@ -156,8 +156,8 @@ const ig5: IndexedGeometry = { width: 50, height: 50, seriesStyle, -}; -const ig6: PointGeometry = { +}); +const ig6 = MockPointGeometry.default({ seriesIdentifier: { specId: 'ig5', key: '', @@ -180,7 +180,8 @@ const ig6: PointGeometry = { x: 0, y: 0, }, -}; +}); + describe('Interaction utils', () => { const chartDimensions: Dimensions = { width: 200, diff --git a/src/chart_types/xy_chart/utils/interactions.ts b/src/chart_types/xy_chart/utils/interactions.ts index dbd384954f..6d6228d6fa 100644 --- a/src/chart_types/xy_chart/utils/interactions.ts +++ b/src/chart_types/xy_chart/utils/interactions.ts @@ -18,7 +18,7 @@ */ import { Rotation } from '../../../utils/commons'; -import { Dimensions } from '../../../utils/dimensions'; +import { Size } from '../../../utils/dimensions'; import { BarGeometry, PointGeometry, IndexedGeometry, isPointGeometry, isBarGeometry } from '../../../utils/geometry'; /** @@ -29,7 +29,7 @@ import { BarGeometry, PointGeometry, IndexedGeometry, isPointGeometry, isBarGeom * @param chartDimension the chart dimension * @internal */ -export function getOrientedXPosition(xPos: number, yPos: number, chartRotation: Rotation, chartDimension: Dimensions) { +export function getOrientedXPosition(xPos: number, yPos: number, chartRotation: Rotation, chartDimension: Size) { switch (chartRotation) { case 180: return chartDimension.width - xPos; @@ -44,7 +44,7 @@ export function getOrientedXPosition(xPos: number, yPos: number, chartRotation: } /** @internal */ -export function getOrientedYPosition(xPos: number, yPos: number, chartRotation: Rotation, chartDimension: Dimensions) { +export function getOrientedYPosition(xPos: number, yPos: number, chartRotation: Rotation, chartDimension: Size) { switch (chartRotation) { case 180: return chartDimension.height - yPos; diff --git a/src/chart_types/xy_chart/utils/nonstacked_series_utils.test.ts b/src/chart_types/xy_chart/utils/nonstacked_series_utils.test.ts index 471a99d0fd..281f74cd8b 100644 --- a/src/chart_types/xy_chart/utils/nonstacked_series_utils.test.ts +++ b/src/chart_types/xy_chart/utils/nonstacked_series_utils.test.ts @@ -91,21 +91,15 @@ describe('Non-Stacked Series Utils', () => { test('empty data', () => { const store = MockStore.default(); MockStore.addSpecs(EMPTY_DATA_SET, store); - const { - formattedDataSeries: { nonStacked }, - } = computeSeriesDomainsSelector(store.getState()); - expect(nonStacked).toHaveLength(0); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); + expect(formattedDataSeries).toHaveLength(0); }); test('format data without nulls', () => { const store = MockStore.default(); MockStore.addSpecs(STANDARD_DATA_SET, store); - const { - formattedDataSeries: { - nonStacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(dataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0].data[0]).toMatchObject({ initialY0: null, initialY1: 10, x: 0, @@ -113,7 +107,7 @@ describe('Non-Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: null, initialY1: 20, x: 0, @@ -121,7 +115,7 @@ describe('Non-Stacked Series Utils', () => { y1: 20, mark: null, }); - expect(dataSeries[2].data[0]).toMatchObject({ + expect(formattedDataSeries[2].data[0]).toMatchObject({ initialY0: null, initialY1: 30, x: 0, @@ -133,13 +127,9 @@ describe('Non-Stacked Series Utils', () => { test('format data with nulls', () => { const store = MockStore.default(); MockStore.addSpecs(WITH_NULL_DATASET, store); - const { - formattedDataSeries: { - nonStacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: null, initialY1: null, x: 0, @@ -151,13 +141,9 @@ describe('Non-Stacked Series Utils', () => { test('format data without nulls with y0 values', () => { const store = MockStore.default(); MockStore.addSpecs(STANDARD_DATA_SET_WY0, store); - const { - formattedDataSeries: { - nonStacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(dataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0].data[0]).toMatchObject({ initialY0: 2, initialY1: 10, x: 0, @@ -165,7 +151,7 @@ describe('Non-Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: 4, initialY1: 20, x: 0, @@ -173,7 +159,7 @@ describe('Non-Stacked Series Utils', () => { y1: 20, mark: null, }); - expect(dataSeries[2].data[0]).toMatchObject({ + expect(formattedDataSeries[2].data[0]).toMatchObject({ initialY0: 6, initialY1: 30, x: 0, @@ -185,13 +171,9 @@ describe('Non-Stacked Series Utils', () => { test('format data with nulls - fit functions', () => { const store = MockStore.default(); MockStore.addSpecs(WITH_NULL_DATASET_WY0, store); - const { - formattedDataSeries: { - nonStacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(dataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0].data[0]).toMatchObject({ initialY0: 2, initialY1: 10, x: 0, @@ -199,7 +181,7 @@ describe('Non-Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: null, initialY1: null, x: 0, @@ -207,7 +189,7 @@ describe('Non-Stacked Series Utils', () => { y0: null, mark: null, }); - expect(dataSeries[2].data[0]).toMatchObject({ + expect(formattedDataSeries[2].data[0]).toMatchObject({ initialY0: 6, initialY1: 30, x: 0, @@ -219,19 +201,15 @@ describe('Non-Stacked Series Utils', () => { test('format data without nulls on second series', () => { const store = MockStore.default(); MockStore.addSpecs(DATA_SET_WITH_NULL_2, store); - const { - formattedDataSeries: { - nonStacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(dataSeries.length).toBe(2); + expect(formattedDataSeries.length).toBe(2); // this because linear non stacked area/lines doesn't fill up the dataset // with missing x data points - expect(dataSeries[0].data.length).toBe(3); - expect(dataSeries[1].data.length).toBe(2); + expect(formattedDataSeries[0].data.length).toBe(3); + expect(formattedDataSeries[1].data.length).toBe(2); - expect(dataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0].data[0]).toMatchObject({ initialY0: null, initialY1: 1, x: 1, @@ -239,7 +217,7 @@ describe('Non-Stacked Series Utils', () => { y1: 1, mark: null, }); - expect(dataSeries[0].data[1]).toMatchObject({ + expect(formattedDataSeries[0].data[1]).toMatchObject({ initialY0: null, initialY1: 2, x: 2, @@ -247,7 +225,7 @@ describe('Non-Stacked Series Utils', () => { y1: 2, mark: null, }); - expect(dataSeries[0].data[2]).toMatchObject({ + expect(formattedDataSeries[0].data[2]).toMatchObject({ initialY0: null, initialY1: 4, x: 4, @@ -255,7 +233,7 @@ describe('Non-Stacked Series Utils', () => { y1: 4, mark: null, }); - expect(dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: null, initialY1: 21, x: 1, @@ -263,7 +241,7 @@ describe('Non-Stacked Series Utils', () => { y1: 21, mark: null, }); - expect(dataSeries[1].data[1]).toMatchObject({ + expect(formattedDataSeries[1].data[1]).toMatchObject({ initialY0: null, initialY1: 23, x: 3, diff --git a/src/chart_types/xy_chart/utils/panel.ts b/src/chart_types/xy_chart/utils/panel.ts new file mode 100644 index 0000000000..218f0894c9 --- /dev/null +++ b/src/chart_types/xy_chart/utils/panel.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Size } from '../../../utils/dimensions'; +import { SmallMultipleScales } from '../state/selectors/compute_small_multiple_scales'; + +/** @internal */ +export function getPanelSize({ horizontal, vertical }: SmallMultipleScales): Size { + return { width: horizontal.bandwidth, height: vertical.bandwidth }; +} diff --git a/src/chart_types/xy_chart/utils/panel_utils.ts b/src/chart_types/xy_chart/utils/panel_utils.ts new file mode 100644 index 0000000000..686f61e4a5 --- /dev/null +++ b/src/chart_types/xy_chart/utils/panel_utils.ts @@ -0,0 +1,59 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Point } from '../../../utils/point'; +import { SmallMultipleScales } from '../state/selectors/compute_small_multiple_scales'; + +/** @internal */ +export interface PerPanelMap { + panelAnchor: Point; + horizontalValue: any; + verticalValue: any; +} + +/** @internal */ +export function getPerPanelMap( + scales: SmallMultipleScales, + fn: (panelAnchor: Point, horizontalValue: any, verticalValue: any, scales: SmallMultipleScales) => T | null, +): Array { + const { horizontal, vertical } = scales; + return vertical.domain.reduce>((acc, verticalValue) => { + return [ + ...acc, + ...horizontal.domain.reduce>((hAcc, horizontalValue) => { + const panelAnchor: Point = { + x: horizontal.scale(horizontalValue) ?? 0, + y: vertical.scale(verticalValue) ?? 0, + }; + const fnObj = fn(panelAnchor, horizontalValue, verticalValue, scales); + if (!fnObj) { + return hAcc; + } + return [ + ...hAcc, + { + panelAnchor, + horizontalValue, + verticalValue, + ...fnObj, + }, + ]; + }, []), + ]; + }, []); +} diff --git a/src/chart_types/xy_chart/utils/scales.test.ts b/src/chart_types/xy_chart/utils/scales.test.ts index 6f3454440b..e58de0c17a 100644 --- a/src/chart_types/xy_chart/utils/scales.test.ts +++ b/src/chart_types/xy_chart/utils/scales.test.ts @@ -19,8 +19,7 @@ import { ScaleType } from '../../../scales/constants'; import { XDomain } from '../domains/types'; -import { computeXScale, countBarsInCluster } from './scales'; -import { FormattedDataSeries } from './series'; +import { computeXScale } from './scales'; describe('Series scales', () => { const xDomainLinear: XDomain = { @@ -44,7 +43,7 @@ describe('Series scales', () => { const expectedBandwidth = 120 / 4; expect(scale.bandwidth).toBe(120 / 4); expect(scale.scale(0)).toBe(0); - expect(scale.scale(1)).toBe(expectedBandwidth * 1); + expect(scale.scale(1)).toBe(expectedBandwidth); expect(scale.scale(2)).toBe(expectedBandwidth * 2); expect(scale.scale(3)).toBe(expectedBandwidth * 3); }); @@ -55,8 +54,8 @@ describe('Series scales', () => { expect(scale.bandwidth).toBe(expectedBandwidth); expect(scale.scale(0)).toBe(expectedBandwidth * 3); expect(scale.scale(1)).toBe(expectedBandwidth * 2); - expect(scale.scale(2)).toBe(expectedBandwidth * 1); - expect(scale.scale(3)).toBe(expectedBandwidth * 0); + expect(scale.scale(2)).toBe(expectedBandwidth); + expect(scale.scale(3)).toBe(0); }); describe('computeXScale with single value domain', () => { @@ -121,70 +120,71 @@ describe('Series scales', () => { expect(zeroGroupScale.bandwidth).toBe(expectedBandwidth); }); - test('count bars required on a cluster', () => { - const stacked: FormattedDataSeries[] = [ - { - groupId: 'g1', - dataSeries: [], - counts: { - area: 10, - bar: 2, - line: 2, - bubble: 0, - }, - }, - { - groupId: 'g2', - dataSeries: [], - counts: { - area: 10, - bar: 20, - line: 2, - bubble: 0, - }, - }, - { - groupId: 'g3', - dataSeries: [], - counts: { - area: 10, - bar: 0, - line: 2, - bubble: 0, - }, - }, - ]; - const nonStacked: FormattedDataSeries[] = [ - { - groupId: 'g1', - dataSeries: [], - counts: { - area: 10, - bar: 5, - line: 2, - bubble: 0, - }, - }, - { - groupId: 'g2', - dataSeries: [], - counts: { - area: 10, - bar: 7, - line: 2, - bubble: 0, - }, - }, - ]; - const { nonStackedBarsInCluster, stackedBarsInCluster, totalBarsInCluster } = countBarsInCluster( - stacked, - nonStacked, - ); - expect(nonStackedBarsInCluster).toBe(12); - // count one per group - expect(stackedBarsInCluster).toBe(2); - expect(totalBarsInCluster).toBe(14); - }); + // test('count bars required on a cluster', () => { + // const stacked: FormattedDataSeries[] = [ + // { + // groupId: 'g1', + // dataSeries: [], + // counts: { + // area: 10, + // bar: 2, + // line: 2, + // bubble: 0, + // }, + // }, + // { + // groupId: 'g2', + // dataSeries: [], + // counts: { + // area: 10, + // bar: 20, + // line: 2, + // bubble: 0, + // }, + // }, + // { + // groupId: 'g3', + // dataSeries: [], + // counts: { + // area: 10, + // bar: 0, + // line: 2, + // bubble: 0, + // }, + // }, + // ]; + // const nonStacked: FormattedDataSeries[] = [ + // { + // groupId: 'g1', + // dataSeries: [], + // counts: { + // area: 10, + // bar: 5, + // line: 2, + // bubble: 0, + // }, + // }, + // { + // groupId: 'g2', + // dataSeries: [], + // counts: { + // area: 10, + // bar: 7, + // line: 2, + // bubble: 0, + // }, + // }, + // ]; + // const { nonStackedBarsInCluster, stackedBarsInCluster, totalBarsInCluster } = countBarsInCluster( + // stacked, + // nonStacked, + // ); + // expect(nonStackedBarsInCluster).toBe(12); + // // count one per group + // expect(stackedBarsInCluster).toBe(2); + // expect(totalBarsInCluster).toBe(14); + // }); + describe('bandwidth when totalBarsInCluster is greater than 0 or less than 0', () => { const xDomainLinear: XDomain = { type: 'xDomain', diff --git a/src/chart_types/xy_chart/utils/scales.ts b/src/chart_types/xy_chart/utils/scales.ts index 579bf4031e..bfc92ef7e1 100644 --- a/src/chart_types/xy_chart/utils/scales.ts +++ b/src/chart_types/xy_chart/utils/scales.ts @@ -21,37 +21,6 @@ import { Scale, ScaleBand, ScaleContinuous } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; import { GroupId } from '../../../utils/ids'; import { XDomain, YDomain } from '../domains/types'; -import { FormattedDataSeries } from './series'; -import { SeriesTypes } from './specs'; - -/** - * Count the max number of bars in cluster value. - * Doesn't take in consideration areas, lines or points. - * @param stacked all the stacked formatted dataseries - * @param nonStacked all the non-stacked formatted dataseries - * @internal - */ -export function countBarsInCluster( - stacked: FormattedDataSeries[], - nonStacked: FormattedDataSeries[], -): { - nonStackedBarsInCluster: number; - stackedBarsInCluster: number; - totalBarsInCluster: number; -} { - // along x axis, we count one "space" per bar series. - // we ignore the points, areas, lines as they are - // aligned with the x value and doesn't occupy space - const nonStackedBarsInCluster = nonStacked.reduce((acc, ns) => acc + ns.counts[SeriesTypes.Bar], 0); - // count stacked bars groups as 1 per group - const stackedBarsInCluster = stacked.reduce((acc, ns) => acc + (ns.counts[SeriesTypes.Bar] > 0 ? 1 : 0), 0); - const totalBarsInCluster = nonStackedBarsInCluster + stackedBarsInCluster; - return { - nonStackedBarsInCluster, - stackedBarsInCluster, - totalBarsInCluster, - }; -} function getBandScaleRange( isInverse: boolean, diff --git a/src/chart_types/xy_chart/utils/series.test.ts b/src/chart_types/xy_chart/utils/series.test.ts index 1404d6e040..65d2e00971 100644 --- a/src/chart_types/xy_chart/utils/series.test.ts +++ b/src/chart_types/xy_chart/utils/series.test.ts @@ -18,6 +18,7 @@ */ import { ChartTypes } from '../..'; +import { MockDataSeries } from '../../../mocks/series'; import { MockSeriesIdentifier } from '../../../mocks/series/series_identifiers'; import { MockSeriesSpec, MockGlobalSpec } from '../../../mocks/specs'; import { MockStore } from '../../../mocks/store'; @@ -28,113 +29,128 @@ import { AccessorFn } from '../../../utils/accessor'; import { Position } from '../../../utils/commons'; import * as TestDataset from '../../../utils/data_samples/test_dataset'; import { ColorConfig } from '../../../utils/themes/theme'; -import { splitSpecsByGroupId } from '../domains/y_domain'; import { computeSeriesDomainsSelector } from '../state/selectors/compute_series_domains'; import { SeriesCollectionValue, - getFormattedDataseries, + getFormattedDataSeries, getSeriesColors, getSortedDataSeriesColorsValuesMap, - getDataSeriesBySpecId, - splitSeriesDataByAccessors, + getDataSeriesFromSpecs, XYChartSeriesIdentifier, - extractYandMarkFromDatum, + extractYAndMarkFromDatum, getSeriesName, DataSeries, + splitSeriesDataByAccessors, } from './series'; import { BasicSeriesSpec, LineSeriesSpec, SeriesTypes, AreaSeriesSpec } from './specs'; import { formatStackedDataSeriesValues } from './stacked_series_utils'; const dg = new SeededDataGenerator(); +function matchOnlyDataSeriesLegacySnapshot(d: DataSeries) { + const { + spec, + groupId, + isStacked, + seriesType, + smVerticalAccessorValue, + smHorizontalAccessorValue, + stackMode, + ...rest + } = d; + return { + ...rest, + }; +} + describe('Series', () => { test('Can split dataset into 1Y0G series', () => { const splitSeries = splitSeriesDataByAccessors( - { + MockSeriesSpec.bar({ id: 'spec1', data: TestDataset.BARCHART_1Y0G, xAccessor: 'x', - yAccessors: ['y1'], - splitSeriesAccessors: ['y'], - }, + yAccessors: ['y'], + }), new Map(), ); - expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); + expect([...splitSeries.dataSeries.values()].map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can split dataset into 1Y1G series', () => { const splitSeries = splitSeriesDataByAccessors( - { + MockSeriesSpec.bar({ id: 'spec1', data: TestDataset.BARCHART_1Y1G, xAccessor: 'x', yAccessors: ['y'], - }, + splitSeriesAccessors: ['g'], + }), new Map(), ); - expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); + expect([...splitSeries.dataSeries.values()].map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can split dataset into 1Y2G series', () => { const splitSeries = splitSeriesDataByAccessors( - { + MockSeriesSpec.bar({ id: 'spec1', data: TestDataset.BARCHART_1Y2G, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g1', 'g2'], - }, + }), new Map(), ); - expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); + expect([...splitSeries.dataSeries.values()].map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can split dataset into 2Y0G series', () => { const splitSeries = splitSeriesDataByAccessors( - { + MockSeriesSpec.bar({ id: 'spec1', data: TestDataset.BARCHART_2Y0G, xAccessor: 'x', yAccessors: ['y1', 'y2'], - }, + }), new Map(), ); - expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); + expect([...splitSeries.dataSeries.values()].map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can split dataset into 2Y1G series', () => { const splitSeries = splitSeriesDataByAccessors( - { + MockSeriesSpec.bar({ id: 'spec1', data: TestDataset.BARCHART_2Y1G, xAccessor: 'x', yAccessors: ['y1', 'y2'], splitSeriesAccessors: ['g'], - }, + }), new Map(), ); - expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); + expect([...splitSeries.dataSeries.values()].map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can split dataset into 2Y2G series', () => { const splitSeries = splitSeriesDataByAccessors( - { + MockSeriesSpec.bar({ id: 'spec1', data: TestDataset.BARCHART_2Y2G, xAccessor: 'x', yAccessors: ['y1', 'y2'], splitSeriesAccessors: ['g1', 'g2'], - }, + }), new Map(), ); - expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); + expect([...splitSeries.dataSeries.values()].map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); it('should get sum of all xValues', () => { const xValueSums = new Map(); splitSeriesDataByAccessors( - { + MockSeriesSpec.bar({ id: 'spec1', data: TestDataset.BARCHART_1Y1G_ORDINAL, xAccessor: 'x', yAccessors: ['y'], splitSeriesAccessors: ['g'], - }, + }), xValueSums, ); expect(xValueSums).toEqual( @@ -167,15 +183,13 @@ describe('Series', () => { store, ); - const { - formattedDataSeries: { stacked }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(stacked[0].dataSeries).toMatchSnapshot(); + expect(formattedDataSeries.map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can stack multiple dataseries', () => { const dataSeries: DataSeries[] = [ - { + MockDataSeries.default({ specId: 'spec1', yAccessor: 'y1', splitAccessors: new Map(), @@ -187,8 +201,8 @@ describe('Series', () => { { x: 3, y1: 3, mark: null, y0: null, initialY1: 3, initialY0: null, datum: undefined }, { x: 4, y1: 4, mark: null, y0: null, initialY1: 4, initialY0: null, datum: undefined }, ], - }, - { + }), + MockDataSeries.default({ specId: 'spec1', yAccessor: 'y1', splitAccessors: new Map(), @@ -200,8 +214,8 @@ describe('Series', () => { { x: 3, y1: 3, mark: null, y0: null, initialY1: 3, initialY0: null, datum: undefined }, { x: 4, y1: 4, mark: null, y0: null, initialY1: 4, initialY0: null, datum: undefined }, ], - }, - { + }), + MockDataSeries.default({ specId: 'spec1', yAccessor: 'y1', splitAccessors: new Map(), @@ -213,8 +227,8 @@ describe('Series', () => { { x: 3, y1: 3, mark: null, y0: null, initialY1: 3, initialY0: null, datum: undefined }, { x: 4, y1: 4, mark: null, y0: null, initialY1: 4, initialY0: null, datum: undefined }, ], - }, - { + }), + MockDataSeries.default({ specId: 'spec1', yAccessor: 'y1', splitAccessors: new Map(), @@ -226,11 +240,11 @@ describe('Series', () => { { x: 3, y1: 3, mark: null, y0: null, initialY1: 3, initialY0: null, datum: undefined }, { x: 4, y1: 4, mark: null, y0: null, initialY1: 4, initialY0: null, datum: undefined }, ], - }, + }), ]; const xValues = new Set([1, 2, 3, 4]); const stackedValues = formatStackedDataSeriesValues(dataSeries, xValues); - expect(stackedValues).toMatchSnapshot(); + expect(stackedValues.map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can stack unsorted dataseries', () => { const store = MockStore.default(); @@ -251,16 +265,14 @@ describe('Series', () => { }), store, ); - const { - formattedDataSeries: { stacked }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(stacked[0].dataSeries).toMatchSnapshot(); + expect(formattedDataSeries.map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can stack high volume of dataseries', () => { const maxArrayItems = 1000; const dataSeries: DataSeries[] = [ - { + MockDataSeries.default({ specId: 'spec1', yAccessor: 'y1', splitAccessors: new Map(), @@ -269,8 +281,8 @@ describe('Series', () => { data: new Array(maxArrayItems) .fill(0) .map((d, i) => ({ x: i, y1: i, mark: null, y0: null, initialY1: i, initialY0: null, datum: undefined })), - }, - { + }), + MockDataSeries.default({ specId: 'spec1', yAccessor: 'y1', splitAccessors: new Map(), @@ -279,11 +291,11 @@ describe('Series', () => { data: new Array(maxArrayItems) .fill(0) .map((d, i) => ({ x: i, y1: i, mark: null, y0: null, initialY1: i, initialY0: null, datum: undefined })), - }, + }), ]; const xValues = new Set(new Array(maxArrayItems).fill(0).map((d, i) => i)); const stackedValues = formatStackedDataSeriesValues(dataSeries, xValues); - expect(stackedValues).toMatchSnapshot(); + expect(stackedValues.map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can stack simple dataseries with scale to extent', () => { const store = MockStore.default(); @@ -312,8 +324,8 @@ describe('Series', () => { store, ); - const seriesDomains = computeSeriesDomainsSelector(store.getState()); - expect(seriesDomains.formattedDataSeries.stacked[0].dataSeries).toMatchSnapshot(); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); + expect(formattedDataSeries.map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can stack multiple dataseries with scale to extent', () => { const store = MockStore.default(); @@ -353,8 +365,8 @@ describe('Series', () => { store, ); - const seriesDomains = computeSeriesDomainsSelector(store.getState()); - expect(seriesDomains.formattedDataSeries.stacked[0].dataSeries).toMatchSnapshot(); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); + expect(formattedDataSeries.map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can stack simple dataseries with y0', () => { const store = MockStore.default(); @@ -386,8 +398,8 @@ describe('Series', () => { store, ); - const seriesDomains = computeSeriesDomainsSelector(store.getState()); - expect(seriesDomains.formattedDataSeries.stacked[0].dataSeries).toMatchSnapshot(); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); + expect(formattedDataSeries.map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can stack simple dataseries with scale to extent with y0', () => { const store = MockStore.default(); @@ -419,8 +431,8 @@ describe('Series', () => { store, ); - const seriesDomains = computeSeriesDomainsSelector(store.getState()); - expect(seriesDomains.formattedDataSeries.stacked[0].dataSeries).toMatchSnapshot(); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); + expect(formattedDataSeries.map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('should split an array of specs into data series', () => { @@ -452,9 +464,9 @@ describe('Series', () => { hideInLegend: false, }; - const splittedDataSeries = getDataSeriesBySpecId([spec1, spec2]); - expect(splittedDataSeries.dataSeriesBySpecId.get('spec1')).toMatchSnapshot(); - expect(splittedDataSeries.dataSeriesBySpecId.get('spec2')).toMatchSnapshot(); + const { dataSeries } = getDataSeriesFromSpecs([spec1, spec2]); + expect(dataSeries.filter(({ specId }) => specId === 'spec1')).toMatchSnapshot(); + expect(dataSeries.filter(({ specId }) => specId === 'spec2')).toMatchSnapshot(); }); test('should compute data series for stacked specs', () => { const spec1: BasicSeriesSpec = { @@ -485,17 +497,11 @@ describe('Series', () => { hideInLegend: false, }; const xValues = new Set([0, 1, 2, 3]); - const splittedDataSeries = getDataSeriesBySpecId([spec1, spec2]); - const specsByGroupIds = splitSpecsByGroupId([spec1, spec2]); - - const stackedDataSeries = getFormattedDataseries( - splittedDataSeries.dataSeriesBySpecId, - xValues, - ScaleType.Linear, - [spec1, spec2], - specsByGroupIds, - ); - expect(stackedDataSeries.stacked).toMatchSnapshot(); + + const { dataSeries } = getDataSeriesFromSpecs([spec1, spec2]); + const stackedDataSeries = getFormattedDataSeries([spec1, spec2], dataSeries, xValues, ScaleType.Linear); + + expect(stackedDataSeries.map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); describe('#getSeriesColors', () => { @@ -565,12 +571,12 @@ describe('Series', () => { }); }); test('should only include deselectedDataSeries when splitting series if deselectedDataSeries is defined', () => { - const specId = 'splitSpec'; + const id = 'splitSpec'; const splitSpec: BasicSeriesSpec = { specType: SpecTypes.Series, chartType: ChartTypes.XYAxis, - id: specId, + id, groupId: 'group', seriesType: SeriesTypes.Line, yScaleType: ScaleType.Log, @@ -582,23 +588,24 @@ describe('Series', () => { hideInLegend: false, }; - const allSeries = getDataSeriesBySpecId([splitSpec]); - expect(allSeries.dataSeriesBySpecId.get(specId)?.length).toBe(2); + const allSeries = getDataSeriesFromSpecs([splitSpec]); + expect(allSeries.dataSeries.filter(({ specId }) => specId === id)).toHaveLength(2); - const emptyDeselected = getDataSeriesBySpecId([splitSpec]); - expect(emptyDeselected.dataSeriesBySpecId.get(specId)?.length).toBe(2); + const emptyDeselected = getDataSeriesFromSpecs([splitSpec]); + expect(emptyDeselected.dataSeries.filter(({ specId }) => specId === id)).toHaveLength(2); const deselectedDataSeries: XYChartSeriesIdentifier[] = [ { - specId, + specId: id, yAccessor: splitSpec.yAccessors[0], splitAccessors: new Map(), seriesKeys: [], - key: 'spec{splitSpec}yAccessor{y1}splitAccessors{}', + key: + 'groupId{group}spec{splitSpec}yAccessor{y1}splitAccessors{}smV{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}smH{__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__}', }, ]; - const subsetSplit = getDataSeriesBySpecId([splitSpec], deselectedDataSeries); - expect(subsetSplit.dataSeriesBySpecId.get(specId)?.length).toBe(1); + const subsetSplit = getDataSeriesFromSpecs([splitSpec], deselectedDataSeries); + expect(subsetSplit.dataSeries.filter(({ specId }) => specId === id)).toHaveLength(1); }); test('should sort series color by series spec sort index', () => { @@ -675,26 +682,26 @@ describe('Series', () => { expect(getSortedDataSeriesColorsValuesMap(seriesCollection)).toEqual(undefinedSortedColorValues); }); test('clean datum shall parse string as number for y values', () => { - let datum = extractYandMarkFromDatum([0, 1, 2], 1, [], 2); + let datum = extractYAndMarkFromDatum([0, 1, 2], 1, [], 2); expect(datum).toBeDefined(); expect(datum?.y1).toBe(1); expect(datum?.y0).toBe(2); - datum = extractYandMarkFromDatum([0, '1', 2], 1, [], 2); + datum = extractYAndMarkFromDatum([0, '1', 2], 1, [], 2); expect(datum).toBeDefined(); expect(datum?.y1).toBe(1); expect(datum?.y0).toBe(2); - datum = extractYandMarkFromDatum([0, '1', '2'], 1, [], 2); + datum = extractYAndMarkFromDatum([0, '1', '2'], 1, [], 2); expect(datum).toBeDefined(); expect(datum?.y1).toBe(1); expect(datum?.y0).toBe(2); - datum = extractYandMarkFromDatum([0, 1, '2'], 1, [], 2); + datum = extractYAndMarkFromDatum([0, 1, '2'], 1, [], 2); expect(datum).toBeDefined(); expect(datum?.y1).toBe(1); expect(datum?.y0).toBe(2); - datum = extractYandMarkFromDatum([0, 'invalid', 'invalid'], 1, [], 2); + datum = extractYAndMarkFromDatum([0, 'invalid', 'invalid'], 1, [], 2); expect(datum).toBeDefined(); expect(datum?.y1).toBe(null); expect(datum?.y0).toBe(null); @@ -919,32 +926,32 @@ describe('Series', () => { test('Can split dataset into 2Y2G series', () => { const xAccessor: AccessorFn = (d) => d.x; const splitSeries = splitSeriesDataByAccessors( - { + MockSeriesSpec.bar({ id: 'spec1', data: TestDataset.BARCHART_2Y2G, xAccessor, yAccessors: ['y1', 'y2'], splitSeriesAccessors: ['g1', 'g2'], - }, + }), new Map(), ); expect([...splitSeries.dataSeries.values()].length).toBe(8); - expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); + expect([...splitSeries.dataSeries.values()].map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Can split dataset with custom _all xAccessor', () => { const xAccessor: AccessorFn = () => '_all'; const splitSeries = splitSeriesDataByAccessors( - { + MockSeriesSpec.bar({ id: 'spec1', data: TestDataset.BARCHART_2Y2G, xAccessor, yAccessors: ['y1'], - }, + }), new Map(), ); expect([...splitSeries.dataSeries.values()].length).toBe(1); - expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); + expect([...splitSeries.dataSeries.values()].map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Shall ignore undefined values on splitSeriesAccessors', () => { @@ -966,7 +973,7 @@ describe('Series', () => { }); const splitSeries = splitSeriesDataByAccessors(spec, new Map()); expect([...splitSeries.dataSeries.values()].length).toBe(2); - expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); + expect([...splitSeries.dataSeries.values()].map(matchOnlyDataSeriesLegacySnapshot)).toMatchSnapshot(); }); test('Should ignore series if splitSeriesAccessors are defined but not contained in any datum', () => { const spec = MockSeriesSpec.bar({ diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index d00eff0764..67bc014396 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -19,17 +19,18 @@ import { SeriesIdentifier, SeriesKey } from '../../../commons/series_id'; import { ScaleType } from '../../../scales/constants'; -import { BinAgg, Direction, XScaleType } from '../../../specs'; +import { GroupBySpec, BinAgg, Direction, XScaleType, DEFAULT_SINGLE_PANEL_SM_VALUE } from '../../../specs'; import { OrderBy } from '../../../specs/settings'; import { ColorOverrides } from '../../../state/chart_state'; import { Accessor, AccessorFn, getAccessorValue } from '../../../utils/accessor'; -import { Datum, Color } from '../../../utils/commons'; -import { GroupId, SpecId } from '../../../utils/ids'; +import { Datum, Color, isNil } from '../../../utils/commons'; +import { GroupId } from '../../../utils/ids'; import { Logger } from '../../../utils/logger'; import { ColorConfig } from '../../../utils/themes/theme'; -import { YBasicSeriesSpec } from '../domains/y_domain'; +import { groupSeriesByYGroup, isHistogramEnabled, isStackedSpec } from '../domains/y_domain'; import { LastValues } from '../state/utils/types'; import { applyFitFunctionToDataSeries } from './fit_function_utils'; +import { groupBy } from './group_data_series'; import { BasicSeriesSpec, SeriesTypes, SeriesSpecs, SeriesNameConfigOptions, StackMode } from './specs'; import { formatStackedDataSeriesValues, datumXSortPredicate } from './stacked_series_utils'; @@ -69,12 +70,19 @@ export interface DataSeriesDatum { export interface XYChartSeriesIdentifier extends SeriesIdentifier { yAccessor: string | number; splitAccessors: Map; // does the map have a size vs making it optional + smVerticalAccessorValue?: string | number; + smHorizontalAccessorValue?: string | number; seriesKeys: (string | number)[]; } /** @internal */ export type DataSeries = XYChartSeriesIdentifier & { + groupId: GroupId; + seriesType: SeriesTypes; data: DataSeriesDatum[]; + isStacked: boolean; + stackMode: StackMode | undefined; + spec: Exclude; }; /** @internal */ @@ -113,26 +121,33 @@ export function getSeriesIndex(series: SeriesIdentifier[], target: SeriesIdentif * @internal */ export function splitSeriesDataByAccessors( - { + spec: BasicSeriesSpec, + xValueSums: Map, + isStacked = false, + enableVislibSeriesSort = false, + stackMode?: StackMode, + smallMultiples?: { vertical?: GroupBySpec; horizontal?: GroupBySpec }, +): { + dataSeries: Map; + xValues: Array; + smVValues: Set; + smHValues: Set; +} { + const { + seriesType, id: specId, + groupId, data, xAccessor, yAccessors, y0Accessors, markSizeAccessor, splitSeriesAccessors = [], - }: Pick< - BasicSeriesSpec, - 'id' | 'data' | 'xAccessor' | 'yAccessors' | 'y0Accessors' | 'splitSeriesAccessors' | 'markSizeAccessor' - >, - xValueSums: Map, - enableVislibSeriesSort = false, -): { - dataSeries: Map; - xValues: Array; -} { + } = spec; const dataSeries = new Map(); const xValues: Array = []; + const smVValues: Set = new Set(); + const smHValues: Set = new Set(); const nonNumericValues: any[] = []; if (enableVislibSeriesSort) { @@ -142,29 +157,44 @@ export function splitSeriesDataByAccessors( * The difference from below is that it loops through all the yAsccessors before the data. */ yAccessors.forEach((accessor, index) => { - data.forEach((datum) => { + for (let i = 0; i < data.length; i++) { + const datum = data[i]; const splitAccessors = getSplitAccessors(datum, splitSeriesAccessors); // if splitSeriesAccessors are defined we should have at least one split value to include datum if (splitSeriesAccessors.length > 0 && splitAccessors.size < 1) { - return; + continue; } // skip if the datum is not an object or null if (typeof datum !== 'object' || datum === null) { - return; + continue; } - const x = getAccessorValue(datum, xAccessor); // skip if the x value is not a string or a number if (typeof x !== 'string' && typeof x !== 'number') { - return; + continue; } xValues.push(x); let sum = xValueSums.get(x) ?? 0; - const cleanedDatum = extractYandMarkFromDatum( + // extract small multiples aggregation values + const smH = smallMultiples?.horizontal?.by + ? smallMultiples.horizontal?.by(spec, datum) + : DEFAULT_SINGLE_PANEL_SM_VALUE; + if (!isNil(smH)) { + smHValues.add(smH); + } + + const smV = smallMultiples?.vertical?.by + ? smallMultiples.vertical.by(spec, datum) + : DEFAULT_SINGLE_PANEL_SM_VALUE; + if (!isNil(smV)) { + smVValues.add(smV); + } + + const cleanedDatum = extractYAndMarkFromDatum( datum, accessor, nonNumericValues, @@ -172,54 +202,74 @@ export function splitSeriesDataByAccessors( markSizeAccessor, ); const seriesKeys = [...splitAccessors.values(), accessor]; - const seriesKey = getSeriesKey({ + const seriesIdentifier = { specId, + groupId, + seriesType, yAccessor: accessor, splitAccessors, - }); + smVerticalAccessorValue: smV, + smHorizontalAccessorValue: smH, + stackMode, + }; + const seriesKey = getSeriesKey(seriesIdentifier, groupId); sum += cleanedDatum.y1 ?? 0; - const newDatum = { x, ...cleanedDatum }; + const newDatum = { x, ...cleanedDatum, smH, smV }; const series = dataSeries.get(seriesKey); if (series) { series.data.push(newDatum); } else { dataSeries.set(seriesKey, { - specId, - yAccessor: accessor, - splitAccessors, - data: [newDatum], - key: seriesKey, + ...seriesIdentifier, + isStacked, seriesKeys, + key: seriesKey, + data: [newDatum], + spec, }); } xValueSums.set(x, sum); - }); + } }); } else { - data.forEach((datum) => { + for (let i = 0; i < data.length; i++) { + const datum = data[i]; const splitAccessors = getSplitAccessors(datum, splitSeriesAccessors); // if splitSeriesAccessors are defined we should have at least one split value to include datum if (splitSeriesAccessors.length > 0 && splitAccessors.size < 1) { - return; + continue; } // skip if the datum is not an object or null if (typeof datum !== 'object' || datum === null) { - return; + continue; } - const x = getAccessorValue(datum, xAccessor); - // skip if the x value is not a string or a number if (typeof x !== 'string' && typeof x !== 'number') { - return; + continue; } xValues.push(x); let sum = xValueSums.get(x) ?? 0; + // extract small multiples aggregation values + const smH = smallMultiples?.horizontal?.by + ? smallMultiples.horizontal?.by(spec, datum) + : DEFAULT_SINGLE_PANEL_SM_VALUE; + if (!isNil(smH)) { + smHValues.add(smH); + } + + const smV = smallMultiples?.vertical?.by + ? smallMultiples.vertical.by(spec, datum) + : DEFAULT_SINGLE_PANEL_SM_VALUE; + if (!isNil(smV)) { + smVValues.add(smV); + } + yAccessors.forEach((accessor, index) => { - const cleanedDatum = extractYandMarkFromDatum( + const cleanedDatum = extractYAndMarkFromDatum( datum, accessor, nonNumericValues, @@ -227,29 +277,36 @@ export function splitSeriesDataByAccessors( markSizeAccessor, ); const seriesKeys = [...splitAccessors.values(), accessor]; - const seriesKey = getSeriesKey({ + const seriesIdentifier = { specId, + groupId, + seriesType, yAccessor: accessor, splitAccessors, - }); + smVerticalAccessorValue: smV, + smHorizontalAccessorValue: smH, + stackMode, + }; + const seriesKey = getSeriesKey(seriesIdentifier, groupId); sum += cleanedDatum.y1 ?? 0; - const newDatum = { x, ...cleanedDatum }; + const newDatum = { x, ...cleanedDatum, smH, smV }; const series = dataSeries.get(seriesKey); if (series) { series.data.push(newDatum); } else { dataSeries.set(seriesKey, { - specId, - yAccessor: accessor, - splitAccessors, - data: [newDatum], - key: seriesKey, + ...seriesIdentifier, + isStacked, seriesKeys, + key: seriesKey, + data: [newDatum], + spec, }); } + + xValueSums.set(x, sum); }); - xValueSums.set(x, sum); - }); + } } if (nonNumericValues.length > 0) { @@ -258,10 +315,11 @@ export function splitSeriesDataByAccessors( `(${nonNumericValues.map((v) => JSON.stringify(v)).join(', ')})`, ); } - return { dataSeries, xValues, + smVValues, + smHValues, }; } @@ -269,16 +327,26 @@ export function splitSeriesDataByAccessors( * Gets global series key to id any series as a string * @internal */ -export function getSeriesKey({ - specId, - yAccessor, - splitAccessors, -}: Pick): string { +export function getSeriesKey( + { + specId, + yAccessor, + splitAccessors, + smVerticalAccessorValue, + smHorizontalAccessorValue, + }: Pick< + XYChartSeriesIdentifier, + 'specId' | 'yAccessor' | 'splitAccessors' | 'smVerticalAccessorValue' | 'smHorizontalAccessorValue' + >, + groupId: GroupId, +): string { const joinedAccessors = [...splitAccessors.entries()] .sort(([a], [b]) => (a > b ? 1 : -1)) .map(([key, value]) => `${key}-${value}`) .join('|'); - return `spec{${specId}}yAccessor{${yAccessor}}splitAccessors{${joinedAccessors}}`; + const smV = smVerticalAccessorValue ? `smV{${smVerticalAccessorValue}}` : ''; + const smH = smHorizontalAccessorValue ? `smH{${smHorizontalAccessorValue}}` : ''; + return `groupId{${groupId}}spec{${specId}}yAccessor{${yAccessor}}splitAccessors{${joinedAccessors}}${smV}${smH}`; } /** @@ -302,7 +370,7 @@ function getSplitAccessors(datum: Datum, accessors: Accessor[] = []): Map, +export function getFormattedDataSeries( + seriesSpecs: SeriesSpecs, + availableDataSeries: DataSeries[], xValues: Set, xScaleType: ScaleType, - seriesSpecs: SeriesSpecs, - specsByGroupIdsEntries: Map< - GroupId, - { - stackMode: StackMode | undefined; - stacked: YBasicSeriesSpec[]; - nonStacked: YBasicSeriesSpec[]; - } - >, -): { - stacked: FormattedDataSeries[]; - nonStacked: FormattedDataSeries[]; -} { - const stackedFormattedDataSeries: { - groupId: GroupId; - dataSeries: DataSeries[]; - counts: DataSeriesCounts; - stackMode?: StackMode; - }[] = []; - const nonStackedFormattedDataSeries: { - groupId: GroupId; - dataSeries: DataSeries[]; - counts: DataSeriesCounts; - }[] = []; - - [...specsByGroupIdsEntries.entries()].forEach(([groupId, groupSpecs]) => { - const { stackMode } = groupSpecs; - // format stacked data series - const stackedDataSeries = getDataSeriesBySpecGroup(groupSpecs.stacked, availableDataSeries); - const fittedStackedDataSeries = applyFitFunctionToDataSeries( - getSortedDataSeries(stackedDataSeries.dataSeries, xValues, xScaleType), - seriesSpecs, - xScaleType, - ); - const fittedAndStackedDataSeries = formatStackedDataSeriesValues(fittedStackedDataSeries, xValues, stackMode); - - stackedFormattedDataSeries.push({ - groupId, - counts: stackedDataSeries.counts, - dataSeries: fittedAndStackedDataSeries, - stackMode, - }); +): DataSeries[] { + const histogramEnabled = isHistogramEnabled(seriesSpecs); + + // apply fit function to every data series + const fittedDataSeries = applyFitFunctionToDataSeries( + getSortedDataSeries(availableDataSeries, xValues, xScaleType), + seriesSpecs, + xScaleType, + ); - // format non stacked data series - const nonStackedDataSeries = getDataSeriesBySpecGroup(groupSpecs.nonStacked, availableDataSeries); - const fittedNonStackedDataSeries = applyFitFunctionToDataSeries( - getSortedDataSeries(nonStackedDataSeries.dataSeries, xValues, xScaleType), - seriesSpecs, - xScaleType, - ); - nonStackedFormattedDataSeries.push({ - groupId, - counts: nonStackedDataSeries.counts, - dataSeries: fittedNonStackedDataSeries, - }); - }); - return { - stacked: stackedFormattedDataSeries.filter((ds) => ds.dataSeries.length > 0), - nonStacked: nonStackedFormattedDataSeries.filter((ds) => ds.dataSeries.length > 0), - }; -} + // apply fitting for stacked DataSeries by YGroup, Panel + const stackedDataSeries = fittedDataSeries.filter(({ spec }) => isStackedSpec(spec, histogramEnabled)); + const stackedGroups = groupBy( + stackedDataSeries, + ['smHorizontalAccessorValue', 'smVerticalAccessorValue', 'groupId'], + true, + ); -function getDataSeriesBySpecGroup( - seriesSpecs: YBasicSeriesSpec[], - dataSeries: Map, -): { - dataSeries: DataSeries[]; - counts: DataSeriesCounts; -} { - return seriesSpecs.reduce<{ - dataSeries: DataSeries[]; - counts: DataSeriesCounts; - }>( - (acc, { id, seriesType }) => { - const ds = dataSeries.get(id); - if (!ds) { - return acc; - } + const fittedAndStackedDataSeries = stackedGroups.reduce((acc, dataSeries) => { + const [{ stackMode }] = dataSeries; + const formatted = formatStackedDataSeriesValues(dataSeries, xValues, stackMode); + return [...acc, ...formatted]; + }, []); + // get already fitted non stacked dataSeries + const nonStackedDataSeries = fittedDataSeries.filter(({ spec }) => !isStackedSpec(spec, histogramEnabled)); - acc.dataSeries.push(...ds); - acc.counts[seriesType] += ds.length; - return acc; - }, - { - dataSeries: [], - counts: { - [SeriesTypes.Bar]: 0, - [SeriesTypes.Area]: 0, - [SeriesTypes.Line]: 0, - [SeriesTypes.Bubble]: 0, - }, - }, - ); + return [...fittedAndStackedDataSeries, ...nonStackedDataSeries]; } /** @@ -450,28 +453,39 @@ function getDataSeriesBySpecGroup( * @param seriesSpecs the map for all the series spec * @param deselectedDataSeries the array of deselected/hidden data series * @param enableVislibSeriesSort is optional; if not specified in , + * @param smallMultiples * @internal */ -export function getDataSeriesBySpecId( +export function getDataSeriesFromSpecs( seriesSpecs: BasicSeriesSpec[], deselectedDataSeries: SeriesIdentifier[] = [], orderOrdinalBinsBy?: OrderBy, enableVislibSeriesSort?: boolean, + smallMultiples?: { vertical?: GroupBySpec; horizontal?: GroupBySpec }, ): { - dataSeriesBySpecId: Map; + dataSeries: DataSeries[]; seriesCollection: Map; xValues: Set; + smVValues: Set; + smHValues: Set; fallbackScale?: XScaleType; } { - const dataSeriesBySpecId = new Map(); + let globalDataSeries: DataSeries[] = []; const seriesCollection = new Map(); const mutatedXValueSums = new Map(); // the unique set of values along the x axis const globalXValues: Set = new Set(); + // the unique set of values along for the vertical small multiple grid + let globalSMVValues: Set = new Set(); + // the unique set of values along for the horizontal small multiple grid + let globalSMHValues: Set = new Set(); + let isNumberArray = true; let isOrdinalScale = false; + + const specsByYGroup = groupSeriesByYGroup(seriesSpecs); // eslint-disable-next-line no-restricted-syntax for (const spec of seriesSpecs) { // check scale type and cast to Ordinal if we found at least one series @@ -480,9 +494,18 @@ export function getDataSeriesBySpecId( isOrdinalScale = true; } - const { dataSeries, xValues } = splitSeriesDataByAccessors(spec, mutatedXValueSums, enableVislibSeriesSort); + const specGroup = specsByYGroup.get(spec.groupId); + const isStacked = Boolean(specGroup?.stacked.find(({ id }) => id === spec.id)); + const { dataSeries, xValues, smVValues, smHValues } = splitSeriesDataByAccessors( + spec, + mutatedXValueSums, + isStacked, + enableVislibSeriesSort, + specGroup?.stackMode, + smallMultiples, + ); - // filter deleselected dataseries + // filter deselected DataSeries let filteredDataSeries: DataSeries[] = [...dataSeries.values()]; if (deselectedDataSeries.length > 0) { filteredDataSeries = filteredDataSeries.filter( @@ -490,7 +513,7 @@ export function getDataSeriesBySpecId( ); } - dataSeriesBySpecId.set(spec.id, filteredDataSeries); + globalDataSeries = [...globalDataSeries, ...filteredDataSeries]; const banded = spec.y0Accessors && spec.y0Accessors.length > 0; @@ -513,6 +536,8 @@ export function getDataSeriesBySpecId( } globalXValues.add(xValue); } + globalSMVValues = new Set([...globalSMVValues, ...smVValues]); + globalSMHValues = new Set([...globalSMHValues, ...smHValues]); } const xValues = @@ -528,9 +553,12 @@ export function getDataSeriesBySpecId( ); return { - dataSeriesBySpecId, + dataSeries: globalDataSeries, seriesCollection, + // keep the user order for ordinal scales xValues, + smVValues: globalSMVValues, + smHValues: globalSMHValues, fallbackScale: !isOrdinalScale && !isNumberArray ? ScaleType.Ordinal : undefined, }; } diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index bbedb5e024..329146f700 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -459,6 +459,7 @@ export interface SeriesScales { } /** @public */ + export type BasicSeriesSpec = SeriesSpec & SeriesAccessors & SeriesScales & { diff --git a/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts b/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts index e9b314ad96..aa8951d88f 100644 --- a/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts +++ b/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts @@ -70,18 +70,18 @@ describe('Stacked Series Utils', () => { store, ); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - const { stacked } = formattedDataSeries; - const [data0] = stacked[0].dataSeries[0].data; + + const [data0] = formattedDataSeries[0].data; expect(data0.initialY1).toBe(10); expect(data0.y0).toBe(0); expect(data0.y1).toBe(0.1); - const [data1] = stacked[0].dataSeries[1].data; + const [data1] = formattedDataSeries[1].data; expect(data1.initialY1).toBe(20); expect(data1.y0).toBe(0.1); expect(data1.y1).toBeCloseTo(0.3); - const [data2] = stacked[0].dataSeries[2].data; + const [data2] = formattedDataSeries[2].data; expect(data2.initialY1).toBe(70); expect(data2.y0).toBeCloseTo(0.3); expect(data2.y1).toBe(1); @@ -100,16 +100,14 @@ describe('Stacked Series Utils', () => { ], store, ); - const { - formattedDataSeries: { stacked }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - const [data0] = stacked[0].dataSeries[0].data; + const [data0] = formattedDataSeries[0].data; expect(data0.initialY1).toBe(10); expect(data0.y0).toBe(0); expect(data0.y1).toBe(0.25); - expect(stacked[0].dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: null, initialY1: null, x: 0, @@ -118,7 +116,7 @@ describe('Stacked Series Utils', () => { mark: null, }); - const [data2] = stacked[0].dataSeries[2].data; + const [data2] = formattedDataSeries[2].data; expect(data2.initialY1).toBe(30); expect(data2.y0).toBe(0.25); expect(data2.y1).toBe(1); @@ -138,23 +136,21 @@ describe('Stacked Series Utils', () => { ], store, ); - const { - formattedDataSeries: { stacked }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - const [data0] = stacked[0].dataSeries[0].data; + const [data0] = formattedDataSeries[0].data; expect(data0.initialY0).toBe(2); expect(data0.initialY1).toBe(10); expect(data0.y0).toBe(0.02); expect(data0.y1).toBe(0.1); - const [data1] = stacked[0].dataSeries[1].data; + const [data1] = formattedDataSeries[1].data; expect(data1.initialY0).toBe(4); expect(data1.initialY1).toBe(20); expect(data1.y0).toBe(0.14); expect(data1.y1).toBeCloseTo(0.3, 5); - const [data2] = stacked[0].dataSeries[2].data; + const [data2] = formattedDataSeries[2].data; expect(data2.initialY0).toBe(6); expect(data2.initialY1).toBe(70); expect(data2.y0).toBeCloseTo(0.36); @@ -175,23 +171,21 @@ describe('Stacked Series Utils', () => { ], store, ); - const { - formattedDataSeries: { stacked }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - const [data0] = stacked[0].dataSeries[0].data; + const [data0] = formattedDataSeries[0].data; expect(data0.initialY0).toBe(2); expect(data0.initialY1).toBe(10); expect(data0.y0).toBe(0.02); expect(data0.y1).toBe(0.1); - const [data1] = stacked[0].dataSeries[1].data; + const [data1] = formattedDataSeries[1].data; expect(data1.initialY0).toBe(null); expect(data1.initialY1).toBe(null); expect(data1.y0).toBe(0.1); expect(data1.y1).toBe(0.1); - const [data2] = stacked[0].dataSeries[2].data; + const [data2] = formattedDataSeries[2].data; expect(data2.initialY0).toBe(6); expect(data2.initialY1).toBe(90); expect(data2.y0).toBe(0.16); @@ -213,14 +207,12 @@ describe('Stacked Series Utils', () => { ], store, ); - const { - formattedDataSeries: { stacked }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(stacked[0].dataSeries.length).toBe(2); - expect(stacked[0].dataSeries[0].data.length).toBe(4); - expect(stacked[0].dataSeries[1].data.length).toBe(4); - expect(stacked[0].dataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries).toHaveLength(2); + expect(formattedDataSeries[0].data).toHaveLength(4); + expect(formattedDataSeries[1].data).toHaveLength(4); + expect(formattedDataSeries[0].data[0]).toMatchObject({ initialY0: null, initialY1: 10, x: 1, @@ -228,7 +220,7 @@ describe('Stacked Series Utils', () => { y1: 0.1, mark: null, }); - expect(stacked[0].dataSeries[0].data[1]).toMatchObject({ + expect(formattedDataSeries[0].data[1]).toMatchObject({ initialY0: null, initialY1: 20, x: 2, @@ -236,7 +228,7 @@ describe('Stacked Series Utils', () => { y1: 1, mark: null, }); - expect(stacked[0].dataSeries[0].data[3]).toMatchObject({ + expect(formattedDataSeries[0].data[3]).toMatchObject({ initialY0: null, initialY1: 40, x: 4, @@ -244,7 +236,7 @@ describe('Stacked Series Utils', () => { y1: 1, mark: null, }); - expect(stacked[0].dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: null, initialY1: 90, x: 1, @@ -252,7 +244,7 @@ describe('Stacked Series Utils', () => { y1: 1, mark: null, }); - expect(stacked[0].dataSeries[1].data[1]).toMatchObject({ + expect(formattedDataSeries[1].data[1]).toMatchObject({ initialY0: null, initialY1: null, x: 2, @@ -263,7 +255,7 @@ describe('Stacked Series Utils', () => { x: 2, }, }); - expect(stacked[0].dataSeries[1].data[2]).toMatchObject({ + expect(formattedDataSeries[1].data[2]).toMatchObject({ initialY0: null, initialY1: 30, x: 3, diff --git a/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts b/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts index 3d672578f2..47b626f900 100644 --- a/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts +++ b/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts @@ -97,25 +97,19 @@ describe('Stacked Series Utils', () => { test('with empty values', () => { const store = MockStore.default(); MockStore.addSpecs(EMPTY_DATA_SET, store); - const { - formattedDataSeries: { stacked }, - } = computeSeriesDomainsSelector(store.getState()); - expect(stacked).toHaveLength(0); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); + expect(formattedDataSeries).toHaveLength(0); }); test('with basic values', () => { const store = MockStore.default(); MockStore.addSpecs(STANDARD_DATA_SET, store); - const { - formattedDataSeries: { - stacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); const values = [ - dataSeries[0].data[0].y0, - dataSeries[0].data[0].y1, - dataSeries[1].data[0].y1, - dataSeries[2].data[0].y1, + formattedDataSeries[0].data[0].y0, + formattedDataSeries[0].data[0].y1, + formattedDataSeries[1].data[0].y1, + formattedDataSeries[2].data[0].y1, ]; expect(values).toEqual([0, 10, 30, 60]); }); @@ -129,34 +123,26 @@ describe('Stacked Series Utils', () => { }), store, ); - const { - formattedDataSeries: { - stacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); const values = [ - dataSeries[0].data[0].y0, - dataSeries[0].data[0].y1, - dataSeries[1].data[0].y1, - dataSeries[2].data[0].y1, + formattedDataSeries[0].data[0].y0, + formattedDataSeries[0].data[0].y1, + formattedDataSeries[1].data[0].y1, + formattedDataSeries[2].data[0].y1, ]; expect(values).toEqual([0, 0.16666666666666666, 0.5, 1]); }); test('with null values', () => { const store = MockStore.default(); MockStore.addSpecs(WITH_NULL_DATASET, store); - const { - formattedDataSeries: { - stacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); const values = [ - dataSeries[0].data[0].y0, - dataSeries[0].data[0].y1, - dataSeries[1].data[0].y1, - dataSeries[2].data[0].y1, + formattedDataSeries[0].data[0].y0, + formattedDataSeries[0].data[0].y1, + formattedDataSeries[1].data[0].y1, + formattedDataSeries[2].data[0].y1, ]; expect(values).toEqual([0, 10, 10, 40]); }); @@ -169,17 +155,13 @@ describe('Stacked Series Utils', () => { }), store, ); - const { - formattedDataSeries: { - stacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); const values = [ - dataSeries[0].data[0].y0, - dataSeries[0].data[0].y1, - dataSeries[1].data[0].y1, - dataSeries[2].data[0].y1, + formattedDataSeries[0].data[0].y0, + formattedDataSeries[0].data[0].y1, + formattedDataSeries[1].data[0].y1, + formattedDataSeries[2].data[0].y1, ]; expect(values).toEqual([0, 0.25, 0.25, 1]); }); @@ -188,13 +170,9 @@ describe('Stacked Series Utils', () => { test('format data without nulls', () => { const store = MockStore.default(); MockStore.addSpecs(STANDARD_DATA_SET, store); - const { - formattedDataSeries: { - stacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(dataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0].data[0]).toMatchObject({ initialY0: null, initialY1: 10, x: 0, @@ -202,7 +180,7 @@ describe('Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: null, initialY1: 20, x: 0, @@ -210,7 +188,7 @@ describe('Stacked Series Utils', () => { y1: 30, mark: null, }); - expect(dataSeries[2].data[0]).toMatchObject({ + expect(formattedDataSeries[2].data[0]).toMatchObject({ initialY0: null, initialY1: 30, x: 0, @@ -222,13 +200,9 @@ describe('Stacked Series Utils', () => { test('format data with nulls', () => { const store = MockStore.default(); MockStore.addSpecs(WITH_NULL_DATASET, store); - const { - formattedDataSeries: { - stacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: null, initialY1: null, x: 0, @@ -240,13 +214,9 @@ describe('Stacked Series Utils', () => { test('format data without nulls with y0 values', () => { const store = MockStore.default(); MockStore.addSpecs(STANDARD_DATA_SET_WY0, store); - const { - formattedDataSeries: { - stacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(dataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0].data[0]).toMatchObject({ initialY0: 2, initialY1: 10, x: 0, @@ -254,7 +224,7 @@ describe('Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: 4, initialY1: 20, x: 0, @@ -262,7 +232,7 @@ describe('Stacked Series Utils', () => { y1: 30, mark: null, }); - expect(dataSeries[2].data[0]).toMatchObject({ + expect(formattedDataSeries[2].data[0]).toMatchObject({ initialY0: 6, initialY1: 30, x: 0, @@ -274,13 +244,9 @@ describe('Stacked Series Utils', () => { test('format data with nulls - missing points', () => { const store = MockStore.default(); MockStore.addSpecs(WITH_NULL_DATASET_WY0, store); - const { - formattedDataSeries: { - stacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(dataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0].data[0]).toMatchObject({ initialY0: 2, initialY1: 10, x: 0, @@ -288,7 +254,7 @@ describe('Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: null, initialY1: null, x: 0, @@ -296,7 +262,7 @@ describe('Stacked Series Utils', () => { y0: 10, mark: null, }); - expect(dataSeries[2].data[0]).toMatchObject({ + expect(formattedDataSeries[2].data[0]).toMatchObject({ initialY0: 6, initialY1: 30, x: 0, @@ -308,17 +274,13 @@ describe('Stacked Series Utils', () => { test('format data without nulls on second series', () => { const store = MockStore.default(); MockStore.addSpecs(DATA_SET_WITH_NULL_2, store); - const { - formattedDataSeries: { - stacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(dataSeries.length).toBe(2); - expect(dataSeries[0].data.length).toBe(4); - expect(dataSeries[1].data.length).toBe(4); + expect(formattedDataSeries).toHaveLength(2); + expect(formattedDataSeries[0].data).toHaveLength(4); + expect(formattedDataSeries[1].data).toHaveLength(4); - expect(dataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0].data[0]).toMatchObject({ initialY0: null, initialY1: 1, x: 1, @@ -326,7 +288,7 @@ describe('Stacked Series Utils', () => { y1: 1, mark: null, }); - expect(dataSeries[0].data[1]).toMatchObject({ + expect(formattedDataSeries[0].data[1]).toMatchObject({ initialY0: null, initialY1: 2, x: 2, @@ -334,7 +296,7 @@ describe('Stacked Series Utils', () => { y1: 2, mark: null, }); - expect(dataSeries[0].data[3]).toMatchObject({ + expect(formattedDataSeries[0].data[3]).toMatchObject({ initialY0: null, initialY1: 4, x: 4, @@ -342,7 +304,7 @@ describe('Stacked Series Utils', () => { y1: 4, mark: null, }); - expect(dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: null, initialY1: 21, x: 1, @@ -350,7 +312,7 @@ describe('Stacked Series Utils', () => { y1: 22, mark: null, }); - expect(dataSeries[1].data[2]).toMatchObject({ + expect(formattedDataSeries[1].data[2]).toMatchObject({ initialY0: null, initialY1: 23, x: 3, @@ -375,13 +337,9 @@ describe('Stacked Series Utils', () => { }), store, ); - const { - formattedDataSeries: { - stacked: [{ dataSeries }], - }, - } = computeSeriesDomainsSelector(store.getState()); + const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(dataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0].data[0]).toMatchObject({ initialY0: null, initialY1: 0, x: 1, @@ -389,7 +347,7 @@ describe('Stacked Series Utils', () => { y1: 0, mark: null, }); - expect(dataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1].data[0]).toMatchObject({ initialY0: null, initialY1: 0, x: 1, diff --git a/src/components/__snapshots__/chart.test.tsx.snap b/src/components/__snapshots__/chart.test.tsx.snap index 8cf358a601..47d294136b 100644 --- a/src/components/__snapshots__/chart.test.tsx.snap +++ b/src/components/__snapshots__/chart.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Chart should render the legend name test 1`] = `"
    • test
    "`; +exports[`Chart should render the legend name test 1`] = `"
    • test
    "`; diff --git a/src/mocks/annotations/annotations.ts b/src/mocks/annotations/annotations.ts new file mode 100644 index 0000000000..e0ae6596b9 --- /dev/null +++ b/src/mocks/annotations/annotations.ts @@ -0,0 +1,88 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AnnotationLineProps } from '../../chart_types/xy_chart/annotations/line/types'; +import { AnnotationRectProps } from '../../chart_types/xy_chart/annotations/rect/types'; +import { mergePartial, RecursivePartial } from '../../utils/commons'; + +/** @internal */ +export class MockAnnotationLineProps { + private static readonly base: AnnotationLineProps = { + linePathPoints: { + x1: 0, + y1: 0, + x2: 0, + y2: 0, + }, + panel: { top: 0, left: 0, width: 100, height: 100 }, + details: {}, + }; + + static default(partial?: RecursivePartial) { + return mergePartial(MockAnnotationLineProps.base, partial, { + mergeOptionalPartialValues: true, + }); + } + + static fromPoints(x1 = 0, y1 = 0, x2 = 0, y2 = 0): AnnotationLineProps { + return MockAnnotationLineProps.default({ + linePathPoints: { + x1, + y1, + x2, + y2, + }, + }); + } +} + +/** @internal */ +export class MockAnnotationRectProps { + private static readonly base: AnnotationRectProps = { + rect: { + x: 0, + y: 0, + width: 0, + height: 0, + }, + panel: { + width: 100, + height: 100, + top: 0, + left: 0, + }, + }; + + static default(partial?: RecursivePartial) { + return mergePartial(MockAnnotationRectProps.base, partial, { + mergeOptionalPartialValues: true, + }); + } + + static fromValues(x = 0, y = 0, width = 0, height = 0): AnnotationRectProps { + return MockAnnotationRectProps.default({ + rect: { + x, + y, + width, + height, + }, + }); + } +} diff --git a/src/mocks/geometries.ts b/src/mocks/geometries.ts index 4474e120a4..92df270a2a 100644 --- a/src/mocks/geometries.ts +++ b/src/mocks/geometries.ts @@ -43,9 +43,15 @@ export class MockPointGeometry { datum: { x: 0, y: 0 }, }, transform: { - x: 25, + x: 0, y: 0, }, + panel: { + width: 100, + height: 100, + left: 0, + top: 0, + }, }; static default(partial?: RecursivePartial) { @@ -53,7 +59,7 @@ export class MockPointGeometry { } static fromBaseline(baseline: RecursivePartial, omitKeys: string[] | string = []) { - return function(partial?: RecursivePartial) { + return (partial?: RecursivePartial) => { return omit( mergePartial(MockPointGeometry.base, partial, { mergeOptionalPartialValues: true }, [baseline]), omitKeys, @@ -69,14 +75,7 @@ export class MockBarGeometry { width: 0, height: 0, color, - displayValue: { - fontSize: 10, - text: '', - width: 0, - height: 0, - hideClippedValue: false, - isValueContainedInElement: false, - }, + displayValue: undefined, seriesIdentifier: MockSeriesIdentifier.default(), value: { accessor: 'y0', @@ -86,6 +85,16 @@ export class MockBarGeometry { datum: { x: 0, y: 0 }, }, seriesStyle: barSeriesStyle, + transform: { + x: 0, + y: 0, + }, + panel: { + width: 100, + height: 100, + left: 0, + top: 0, + }, }; static default(partial?: RecursivePartial) { @@ -93,7 +102,7 @@ export class MockBarGeometry { } static fromBaseline(baseline: RecursivePartial, omitKeys: string[] | string = []) { - return function(partial?: RecursivePartial) { + return (partial?: RecursivePartial) => { const geo = mergePartial(MockBarGeometry.base, partial, { mergeOptionalPartialValues: true }, [ baseline, ]); diff --git a/src/mocks/series/series.ts b/src/mocks/series/series.ts index 3351b38ea3..a7c3751547 100644 --- a/src/mocks/series/series.ts +++ b/src/mocks/series/series.ts @@ -26,8 +26,9 @@ import { XYChartSeriesIdentifier, FormattedDataSeries, } from '../../chart_types/xy_chart/utils/series'; -import { DEFAULT_GLOBAL_ID } from '../../specs'; +import { DEFAULT_GLOBAL_ID, SeriesTypes } from '../../specs'; import { mergePartial } from '../../utils/commons'; +import { MockSeriesSpec } from '../specs'; import { getRandomNumberGenerator } from '../utils'; import { fitFunctionData } from './data'; @@ -49,6 +50,11 @@ export class MockDataSeries { splitAccessors: new Map(), key: 'spec1', data: [], + groupId: 'group1', + seriesType: SeriesTypes.Bar, + stackMode: undefined, + spec: MockSeriesSpec.bar(), + isStacked: false, }; static default(partial?: Partial) { diff --git a/src/mocks/series/series_identifiers.ts b/src/mocks/series/series_identifiers.ts index f92688fd78..819b5e6338 100644 --- a/src/mocks/series/series_identifiers.ts +++ b/src/mocks/series/series_identifiers.ts @@ -19,10 +19,10 @@ import { SeriesCollectionValue, - getDataSeriesBySpecId, + getDataSeriesFromSpecs, XYChartSeriesIdentifier, } from '../../chart_types/xy_chart/utils/series'; -import { BasicSeriesSpec } from '../../specs'; +import { BasicSeriesSpec, DEFAULT_SINGLE_PANEL_SM_VALUE } from '../../specs'; import { mergePartial } from '../../utils/commons'; type SeriesCollection = Map; @@ -34,7 +34,7 @@ export class MockSeriesCollection { } static fromSpecs(seriesSpecs: BasicSeriesSpec[]) { - const { seriesCollection } = getDataSeriesBySpecId(seriesSpecs, []); + const { seriesCollection } = getDataSeriesFromSpecs(seriesSpecs, []); return seriesCollection; } @@ -47,7 +47,9 @@ export class MockSeriesIdentifier { yAccessor: 'y', seriesKeys: ['a'], splitAccessors: new Map().set('g', 'a'), - key: 'spec{bars}yAccessor{y}splitAccessors{g-a}', + key: `spec{bars}yAccessor{y}splitAccessors{g-a}smV${DEFAULT_SINGLE_PANEL_SM_VALUE}smH${DEFAULT_SINGLE_PANEL_SM_VALUE}`, + smHorizontalAccessorValue: DEFAULT_SINGLE_PANEL_SM_VALUE, + smVerticalAccessorValue: DEFAULT_SINGLE_PANEL_SM_VALUE, }; static default(partial?: Partial) { @@ -57,8 +59,12 @@ export class MockSeriesIdentifier { } static fromSpecs(specs: BasicSeriesSpec[]): XYChartSeriesIdentifier[] { - const { seriesCollection } = getDataSeriesBySpecId(specs); + const { dataSeries } = getDataSeriesFromSpecs(specs); - return [...seriesCollection.values()].map(({ seriesIdentifier }) => seriesIdentifier); + return dataSeries.map(({ groupId, seriesType, data, isStacked, stackMode, spec, ...rest }) => rest); + } + + static fromSpec(specs: BasicSeriesSpec): XYChartSeriesIdentifier { + return MockSeriesIdentifier.fromSpecs([specs])[0]; } } diff --git a/src/mocks/specs/specs.ts b/src/mocks/specs/specs.ts index f30ebe6641..2159aca0d3 100644 --- a/src/mocks/specs/specs.ts +++ b/src/mocks/specs/specs.ts @@ -197,6 +197,12 @@ export class MockSeriesSpec { }); } + static bubble(partial?: Partial): BubbleSeriesSpec { + return mergePartial(MockSeriesSpec.bubbleBase, partial as RecursivePartial, { + mergeOptionalPartialValues: true, + }); + } + static sunburst(partial?: Partial): PartitionSpec { return mergePartial(MockSeriesSpec.sunburstBase, partial as RecursivePartial, { mergeOptionalPartialValues: true, @@ -272,7 +278,6 @@ export class MockGlobalSpec { showOverlappingTicks: false, showOverlappingLabels: false, position: Position.Left, - tickFormat: (tick: any) => `${tick}`, }; private static readonly settingsBaseNoMargings: SettingsSpec = { diff --git a/src/renderers/canvas/index.ts b/src/renderers/canvas/index.ts index 4f1c462027..58202c15c9 100644 --- a/src/renderers/canvas/index.ts +++ b/src/renderers/canvas/index.ts @@ -55,13 +55,13 @@ export function renderLayers(ctx: CanvasRenderingContext2D, layers: Array<(ctx: /** @internal */ export function withClip( ctx: CanvasRenderingContext2D, - clipppings: Rect, + clippings: Rect, fun: (ctx: CanvasRenderingContext2D) => void, shouldClip = true, ) { withContext(ctx, (ctx) => { if (shouldClip) { - const { x, y, width, height } = clipppings; + const { x, y, width, height } = clippings; ctx.beginPath(); ctx.rect(x, y, width, height); ctx.clip(); diff --git a/src/scales/scale_band.ts b/src/scales/scale_band.ts index d64dc33316..78bd3bb7e4 100644 --- a/src/scales/scale_band.ts +++ b/src/scales/scale_band.ts @@ -53,16 +53,24 @@ export class ScaleBand implements Scale { * A number between 0 and 1. * @defaultValue 0 */ - barsPadding = 0, + barsPadding: number | [number, number] = 0, ) { this.type = ScaleType.Ordinal; this.d3Scale = scaleBand>(); this.d3Scale.domain(domain); this.d3Scale.range(range); - const safeBarPadding = maxValueWithUpperLimit(barsPadding, 0, 1); - this.barsPadding = safeBarPadding; - this.d3Scale.paddingInner(safeBarPadding); - this.d3Scale.paddingOuter(safeBarPadding / 2); + let safeBarPadding = 0; + if (Array.isArray(barsPadding)) { + this.d3Scale.paddingInner(barsPadding[1]); + this.d3Scale.paddingOuter(barsPadding[0]); + this.barsPadding = barsPadding[1]; + } else { + safeBarPadding = maxValueWithUpperLimit(barsPadding, 0, 1); + this.d3Scale.paddingInner(safeBarPadding); + this.barsPadding = safeBarPadding; + this.d3Scale.paddingOuter(safeBarPadding / 2); + } + this.outerPadding = this.d3Scale.paddingOuter(); this.innerPadding = this.d3Scale.paddingInner(); this.bandwidth = this.d3Scale.bandwidth() || 0; diff --git a/src/specs/constants.ts b/src/specs/constants.ts index e4b691647c..c5e2333f6a 100644 --- a/src/specs/constants.ts +++ b/src/specs/constants.ts @@ -29,6 +29,8 @@ export const SpecTypes = Object.freeze({ Axis: 'axis' as const, Annotation: 'annotation' as const, Settings: 'settings' as const, + IndexOrder: 'index_order' as const, + SmallMultiples: 'small_multiples' as const, }); /** @public */ export type SpecTypes = $Values; diff --git a/src/specs/group_by.ts b/src/specs/group_by.ts new file mode 100644 index 0000000000..579851882a --- /dev/null +++ b/src/specs/group_by.ts @@ -0,0 +1,50 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; + +import { Spec } from '.'; +import { ChartTypes } from '../chart_types'; +import { Predicate } from '../chart_types/heatmap/utils/commons'; +import { getConnect, specComponentFactory } from '../state/spec_factory'; +import { SpecTypes } from './constants'; + +/** @alpha */ +export type GroupByAccessor = (spec: Spec, datum: any) => string | number; +/** @alpha */ +export type GroupBySort = Predicate; + +/** @alpha */ +export interface GroupBySpec extends Spec { + by: GroupByAccessor; + sort: GroupBySort; +} +const DEFAULT_GROUP_BY_PROPS = { + chartType: ChartTypes.Global, + specType: SpecTypes.IndexOrder, +}; + +type DefaultGroupByProps = 'chartType' | 'specType'; + +/** @alpha */ +export type GroupByProps = Pick; + +/** @alpha */ +export const GroupBy: React.FunctionComponent = getConnect()( + specComponentFactory(DEFAULT_GROUP_BY_PROPS), +); diff --git a/src/specs/index.ts b/src/specs/index.ts index 057f1c7e5d..c1796ea319 100644 --- a/src/specs/index.ts +++ b/src/specs/index.ts @@ -28,6 +28,9 @@ export interface Spec { specType: string; } +export * from './group_by'; +export * from './small_multiples'; + export * from './settings'; export * from './constants'; export * from '../chart_types/specs'; diff --git a/src/specs/small_multiples.ts b/src/specs/small_multiples.ts new file mode 100644 index 0000000000..b3ef93770a --- /dev/null +++ b/src/specs/small_multiples.ts @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; + +import { Spec } from '.'; +import { ChartTypes } from '../chart_types'; +import { getConnect, specComponentFactory } from '../state/spec_factory'; +import { SpecTypes } from './constants'; + +/** @internal */ +export const DEFAULT_SINGLE_PANEL_SM_VALUE = '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__'; + +/** @internal */ +export const DEFAULT_SM_PANEL_PADDING: [number, number] = [0, 0.1]; + +/** @alpha */ +export interface SmallMultiplesSpec extends Spec { + splitHorizontally?: string; + splitVertically?: string; + style?: { + verticalPanelPadding?: [number, number]; + horizontalPanelPadding?: [number, number]; + }; +} + +const DEFAULT_SMALL_MULTIPLES_PROPS = { + id: '__global__small_multiples___', + chartType: ChartTypes.Global, + specType: SpecTypes.SmallMultiples, +}; + +/** @alpha */ +export type SmallMultiplesProps = Partial>; + +/** @alpha */ +export const SmallMultiples: React.FunctionComponent = getConnect()( + specComponentFactory(DEFAULT_SMALL_MULTIPLES_PROPS), +); diff --git a/src/specs/specs_parser.tsx b/src/specs/specs_parser.tsx index 3b546734bd..fb33f1eba6 100644 --- a/src/specs/specs_parser.tsx +++ b/src/specs/specs_parser.tsx @@ -25,6 +25,7 @@ import { specParsed, specUnmounted } from '../state/actions/specs'; const SpecsParserComponent: React.FunctionComponent = (props) => { const injected = props as DispatchProps; + // clean all specs useEffect(() => { injected.specParsed(); }); diff --git a/src/utils/commons.ts b/src/utils/commons.ts index e82f32d59a..ba7f00392e 100644 --- a/src/utils/commons.ts +++ b/src/utils/commons.ts @@ -262,10 +262,15 @@ export function getAllKeys(object: any, objects: any[] = []): string[] { } /** @internal */ -export function isArrayOrSet(value: any): boolean { +export function isArrayOrSet(value: any): value is Array | Set { return Array.isArray(value) || value instanceof Set; } +/** @internal */ +export function isNil(value: any): value is null | undefined { + return value === null || value === undefined; +} + /** @internal */ export function hasPartialObjectToMerge( base: T, diff --git a/src/utils/dimensions.ts b/src/utils/dimensions.ts index db43736ea3..f621adcc12 100644 --- a/src/utils/dimensions.ts +++ b/src/utils/dimensions.ts @@ -24,6 +24,11 @@ export interface Dimensions { height: number; } +export interface Size { + width: number; + height: number; +} + // fixme consider switching from `number` to `Pixels` or similar, once nominal typing is added export interface PerSideDistance { top: number; diff --git a/src/utils/geometry.ts b/src/utils/geometry.ts index ed9ed1b5a0..46720c5dd6 100644 --- a/src/utils/geometry.ts +++ b/src/utils/geometry.ts @@ -21,6 +21,7 @@ import { $Values } from 'utility-types'; import { XYChartSeriesIdentifier } from '../chart_types/xy_chart/utils/series'; import { Color } from './commons'; +import { Dimensions } from './dimensions'; import { BarSeriesStyle, PointStyle, AreaStyle, LineStyle, ArcStyle } from './themes/theme'; /** @@ -65,12 +66,24 @@ export interface PointGeometry { seriesIdentifier: XYChartSeriesIdentifier; value: GeometryValue; styleOverrides?: Partial; + panel: Dimensions; } + +export interface PerPanel { + panel: Dimensions; + value: T; +} + export interface BarGeometry { x: number; y: number; width: number; height: number; + transform: { + x: number; + y: number; + rotation?: number; + }; color: Color; displayValue?: { fontScale?: number; @@ -84,6 +97,7 @@ export interface BarGeometry { seriesIdentifier: XYChartSeriesIdentifier; value: GeometryValue; seriesStyle: BarSeriesStyle; + panel: Dimensions; } export interface LineGeometry { diff --git a/stories/axes/8_custom_domain.tsx b/stories/axes/8_custom_domain.tsx index c254698e60..3fe1134241 100644 --- a/stories/axes/8_custom_domain.tsx +++ b/stories/axes/8_custom_domain.tsx @@ -91,7 +91,6 @@ export const Example = () => { xAccessor="x" yAccessors={['y']} stackAccessors={['x']} - splitSeriesAccessors={['g']} data={[ { x: 0, y: 3 }, { x: 1, y: 2 }, diff --git a/stories/small_multiples/1_grid.tsx b/stories/small_multiples/1_grid.tsx new file mode 100644 index 0000000000..c371152cea --- /dev/null +++ b/stories/small_multiples/1_grid.tsx @@ -0,0 +1,232 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { boolean } from '@storybook/addon-knobs'; +import React, { useState } from 'react'; + +import { + ScaleType, + Position, + Chart, + Axis, + LineSeries, + GroupBy, + SmallMultiples, + Settings, + BarSeries, + AreaSeries, + Fit, + LineAnnotation, + BubbleSeries, + AnnotationDomainTypes, + Rotation, + RectAnnotation, +} from '../../src'; +import { getRandomNumberGenerator, SeededDataGenerator } from '../../src/mocks/utils'; +import { SB_SOURCE_PANEL } from '../utils/storybook'; + +const getRandomNumber = getRandomNumberGenerator(); +const dg = new SeededDataGenerator(); + +const data1 = dg.generateGroupedSeries(10, 3); +const data2 = dg.generateGroupedSeries(10, 3).map((d) => { + return getRandomNumber() > 0.95 ? { ...d, y: null } : d; +}); +const data3 = dg.generateGroupedSeries(10, 3).map((d) => { + return getRandomNumber() > 0.95 ? { ...d, y: null } : d; +}); + +export const Example = () => { + const splitVertically = boolean('vertical split', true); + const splitHorizontally = boolean('horizontal split', true); + const [rotationIndex, setRotationIndex] = useState(0); + const rot: Rotation = ([0, -90, 90, 0] as Rotation[])[rotationIndex]; + const showLegend = boolean('Show Legend', false); + return ( + <> + g + + + + + d.toFixed(2)} + /> + + { + return id; + }} + sort="alphaAsc" + /> + { + return g; + }} + sort="alphaAsc" + /> + + + + } + style={{ + line: { + stroke: 'red', + strokeWidth: 2, + opacity: 0.8, + }, + }} + zIndex={-10} + /> + + } + style={{ + line: { + stroke: 'blue', + strokeWidth: 5, + opacity: 0.8, + }, + }} + zIndex={-10} + /> + + + + + + + + ); +}; +Example.story = { + parameters: { + options: { selectedPanel: SB_SOURCE_PANEL }, + info: { + text: `If your data is in UTC timezone, your tooltip and axis labels can be configured + to visualize the time translated to your local timezone. You should be able to see the + first value on \`2019-01-01 01:00:00.000 \``, + }, + }, +}; diff --git a/stories/small_multiples/2_vertical_areas.tsx b/stories/small_multiples/2_vertical_areas.tsx new file mode 100644 index 0000000000..7aedf425d1 --- /dev/null +++ b/stories/small_multiples/2_vertical_areas.tsx @@ -0,0 +1,105 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { boolean } from '@storybook/addon-knobs'; +import { DateTime } from 'luxon'; +import React from 'react'; + +import { + ScaleType, + Position, + Chart, + Axis, + GroupBy, + SmallMultiples, + Settings, + AreaSeries, + LIGHT_THEME, + niceTimeFormatByDay, + timeFormatter, +} from '../../src'; +import { SeededDataGenerator } from '../../src/mocks/utils'; +import { SB_SOURCE_PANEL } from '../utils/storybook'; + +const dg = new SeededDataGenerator(); +const numOfDays = 60; +const data = dg.generateGroupedSeries(numOfDays, 6, 'metric ').map((d) => { + return { + ...d, + x: DateTime.fromISO('2020-01-01T00:00:00Z') + .plus({ days: d.x }) + .toMillis(), + }; +}); + +export const Example = () => { + const showLegend = boolean('Show Legend', false); + return ( + + + + d.toFixed(2)} + /> + + { + return g; + }} + sort="alphaDesc" + /> + + + + ); +}; +Example.story = { + parameters: { + options: { selectedPanel: SB_SOURCE_PANEL }, + info: { + text: `The above chart shows an example of small multiples technique that splits our dataset into multiple + sub-series vertically positioned one below the other. + The configuration is obtained by defining a \`\` operation component that define the property used to + divide/group my dataset(via to the \`by\` props) and using the specified \`id\` of that operation inside the + \`\` component. + +Each charts has the same vertical and horizontal axis scale. +`, + }, + }, +}; diff --git a/stories/small_multiples/3_grid_lines.tsx b/stories/small_multiples/3_grid_lines.tsx new file mode 100644 index 0000000000..5ef9316ba0 --- /dev/null +++ b/stories/small_multiples/3_grid_lines.tsx @@ -0,0 +1,163 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { boolean } from '@storybook/addon-knobs'; +import { DateTime } from 'luxon'; +import React from 'react'; + +import { + ScaleType, + Position, + Chart, + Axis, + LineSeries, + GroupBy, + SmallMultiples, + Settings, + LIGHT_THEME, + niceTimeFormatByDay, + timeFormatter, +} from '../../src'; +import { SeededDataGenerator } from '../../src/mocks/utils'; +import { SB_SOURCE_PANEL } from '../utils/storybook'; + +const dg = new SeededDataGenerator(); +const numOfDays = 90; +const groupNames = new Array(16).fill(0).map((d, i) => String.fromCharCode(97 + i)); +const data = dg.generateGroupedSeries(numOfDays, 16).map((d) => { + return { + y: d.y, + x: DateTime.fromISO('2020-01-01T00:00:00Z') + .plus({ days: d.x }) + .toMillis(), + g: d.g, + h: `host ${groupNames.indexOf(d.g) % 4}`, + v: `metric ${Math.floor(groupNames.indexOf(d.g) / 4)}`, + }; +}); + +export const Example = () => { + const showLegend = boolean('Show Legend', false); + return ( + + + + d.toFixed(2)} + /> + + { + return v; + }} + sort="numDesc" + /> + { + return h; + }} + sort="numAsc" + /> + + + { + const val = Number(`${smHorizontalAccessorValue}`.split('host ')[1]); + return LIGHT_THEME.colors.vizColors[val]; + }} + data={data} + /> + + ); +}; +Example.story = { + parameters: { + options: { selectedPanel: SB_SOURCE_PANEL }, + info: { + text: `It is possible to add either a vertical and horizontal \`\` operations to create a grid of +small multiples. +The assignment of the series colors can be handled by defining an accessor in the \`color\` prop of the series that +consider the \`smHorizontalAccessorValue\` or \`smVerticalAccessorValue\` values when returning the assigned color. +`, + }, + }, +}; diff --git a/stories/small_multiples/4_horizontal_bars.tsx b/stories/small_multiples/4_horizontal_bars.tsx new file mode 100644 index 0000000000..40d297e40f --- /dev/null +++ b/stories/small_multiples/4_horizontal_bars.tsx @@ -0,0 +1,159 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { boolean } from '@storybook/addon-knobs'; +import { DateTime } from 'luxon'; +import React from 'react'; + +import { + ScaleType, + Position, + Chart, + Axis, + GroupBy, + SmallMultiples, + Settings, + BarSeries, + LineAnnotation, + AnnotationDomainTypes, + LIGHT_THEME, +} from '../../src'; +import { SeededDataGenerator } from '../../src/mocks/utils'; +import { SB_SOURCE_PANEL } from '../utils/storybook'; + +const dg = new SeededDataGenerator(); +const numOfDays = 7; +function generateData() { + return dg.generateGroupedSeries(numOfDays, 2).map((d) => { + return { + ...d, + x: DateTime.fromFormat(`${d.x + 1}`, 'E').toFormat('EEEE'), + y: Math.floor(d.y * 10), + g: d.g === 'a' ? 'new user' : 'existing user', + }; + }); +} +const data1 = generateData(); +const data2 = generateData(); +const data3 = generateData(); + +export const Example = () => { + const marker = ( + + MIN + + ); + const showLegend = boolean('Show Legend', false); + + return ( + + + + + + { + return spec.id; + }} + sort="alphaAsc" + /> + + + + + + + ); +}; +Example.story = { + parameters: { + options: { selectedPanel: SB_SOURCE_PANEL }, + info: { + text: `Similarly to the Vertical Areas example, the above chart shows an example of small multiples technique +that splits our dataset into multiple sub-series horizontally positioned one aside the other. +In this case, the \`\` id is used to specify the horizontal split via the \`splitHorizontally\` prop. + +As for single charts, we can merge and handle multiple data-series together and specify a \`by\` accessor to consider +the specific case. An additional property \`sort\` is available to configure the sorting order of the vertical or +horizontal split. +`, + }, + }, +}; diff --git a/stories/small_multiples/small_multiples.stories.tsx b/stories/small_multiples/small_multiples.stories.tsx new file mode 100644 index 0000000000..de4b73c45f --- /dev/null +++ b/stories/small_multiples/small_multiples.stories.tsx @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SB_KNOBS_PANEL } from '../utils/storybook'; + +export default { + title: 'Small Multiples (@alpha)', + parameters: { + options: { selectedPanel: SB_KNOBS_PANEL }, + }, +}; + +export { Example as verticalAreas } from './2_vertical_areas'; +export { Example as horizontalBars } from './4_horizontal_bars'; +export { Example as gridLines } from './3_grid_lines'; diff --git a/yarn.lock b/yarn.lock index 28e8ce9416..dc4d2422fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5520,9 +5520,9 @@ integrity sha512-+NPqjXgyA02xTHKJDeDca9u8Zr42ts6jhdND4C3PrPeQ35RJa0dmfAedXW7a9K4N1QcBbuWI1nSfGK4r1eVFCQ== "@types/d3-array@^1.2.6": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.2.7.tgz#34dc654d34fc058c41c31dbca1ed68071a8fcc17" - integrity sha512-51vHWuUyDOi+8XuwPrTw3cFqyh2Slg9y8COYkRfjCPG9TfYqY0hoNPzv/8BrcAy0FeQBzqEo/D/8Nk2caOQJnA== + version "1.2.8" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.2.8.tgz#b852381cb68e31e46bfa23ee70a383cbc6d62146" + integrity sha512-wWV0wT6oLUGprrOR5LMK7Dh8EBiondhnqINsvazv6UucYfTdb2oaFF4knlqzZV2RKB9ZC9G7G1Iojt8b/wolsw== "@types/d3-collection@^1.0.8": version "1.0.8"