diff --git a/.eslintignore b/.eslintignore index 2326eeadcf..24e6c52fc2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -9,3 +9,4 @@ /dist /coverage /.vscode +src/utils/d3-delaunay/* diff --git a/.prettierignore b/.prettierignore index 183298d5ef..86b2da9da2 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,2 @@ *.md -*.mdx \ No newline at end of file +*.mdx diff --git a/NOTICE.txt b/NOTICE.txt index f91eb118a2..f038002ddf 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -21,4 +21,22 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. + +--- +This product also includes code that is adapted from d3-delaunay@5.2.1, +which is available under a "ISC" license. + +Copyright 2018 Observable, Inc. + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-area-chart-band-area-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-area-chart-band-area-visually-looks-correct-1-snap.png index ab04b7710a..c5c5662f5e 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-area-chart-band-area-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-area-chart-band-area-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-fit-domain-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-fit-domain-visually-looks-correct-1-snap.png index 627ee9f583..93acc08f76 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-fit-domain-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-fit-domain-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-many-tick-labels-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-many-tick-labels-visually-looks-correct-1-snap.png index dd7406f079..0d5d2e554c 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-many-tick-labels-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-many-tick-labels-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-band-bar-chart-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-band-bar-chart-visually-looks-correct-1-snap.png index ad5bc0404d..eb4f196189 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-band-bar-chart-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-band-bar-chart-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-with-high-data-volume-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-with-high-data-volume-visually-looks-correct-1-snap.png index bb0ce456eb..5cbde6eec8 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-with-high-data-volume-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-with-high-data-volume-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-alpha-mixed-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-alpha-mixed-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..73a1b21da7 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-alpha-mixed-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-alpha-multiple-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-alpha-multiple-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..0502f3c2a2 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-alpha-multiple-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-alpha-ordinal-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-alpha-ordinal-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..7744ec5bc1 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-alpha-ordinal-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-alpha-simple-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-alpha-simple-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..7ae5d4db1d Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-alpha-simple-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-mixed-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-mixed-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..9e28c26223 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-mixed-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-multiple-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-multiple-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..0502f3c2a2 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-multiple-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-simple-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-simple-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..7ae5d4db1d Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-chart-simple-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-charts-mixed-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-charts-mixed-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..d94a9fae49 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-charts-mixed-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-charts-multiple-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-charts-multiple-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..851629deb5 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-charts-multiple-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-charts-simple-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-charts-simple-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..7ae5d4db1d Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bubble-charts-simple-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-mixed-charts-bubble-chart-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-mixed-charts-bubble-chart-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..9ecd85f3ee Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-mixed-charts-bubble-chart-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-mixed-charts-mark-size-accessor-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-mixed-charts-mark-size-accessor-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..48782e5425 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-mixed-charts-mark-size-accessor-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-chart-size-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-chart-size-visually-looks-correct-1-snap.png index 6d4df86b46..bbf2accd1e 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-chart-size-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-chart-size-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-multiple-custom-partial-themes-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-multiple-custom-partial-themes-visually-looks-correct-1-snap.png index 81e3f0a0bf..3613abbf3e 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-multiple-custom-partial-themes-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-multiple-custom-partial-themes-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-partial-custom-theme-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-partial-custom-theme-visually-looks-correct-1-snap.png index 272a24227b..062d496a2e 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-partial-custom-theme-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-partial-custom-theme-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-partial-custom-theme-with-base-theme-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-partial-custom-theme-with-base-theme-visually-looks-correct-1-snap.png index 272a24227b..062d496a2e 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-partial-custom-theme-with-base-theme-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-partial-custom-theme-with-base-theme-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-theme-styling-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-theme-styling-visually-looks-correct-1-snap.png index 42d2f06ab1..7ade3ca23f 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-theme-styling-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-theme-styling-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-accessor-formats-should-show-custom-format-1-snap.png b/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-accessor-formats-should-show-custom-format-1-snap.png index 5aab687f87..9e423436f0 100644 Binary files a/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-accessor-formats-should-show-custom-format-1-snap.png and b/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-accessor-formats-should-show-custom-format-1-snap.png differ 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 d77a32f70a..98cdf7e322 100644 Binary files a/integration/tests/__image_snapshots__/axis-stories-test-ts-axis-stories-should-render-tick-padding-1-snap.png and b/integration/tests/__image_snapshots__/axis-stories-test-ts-axis-stories-should-render-tick-padding-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-first-x-value-bottom-1-snap.png b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-first-x-value-bottom-1-snap.png index c3298e6bb5..fa88446b0c 100644 Binary files a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-first-x-value-bottom-1-snap.png and b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-first-x-value-bottom-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-first-x-value-top-1-snap.png b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-first-x-value-top-1-snap.png index 0049cf8488..e8dc1ad8ff 100644 Binary files a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-first-x-value-top-1-snap.png and b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-first-x-value-top-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-last-x-value-bottom-1-snap.png b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-last-x-value-bottom-1-snap.png index 4d7528b7ea..452d1c0494 100644 Binary files a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-last-x-value-bottom-1-snap.png and b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-last-x-value-bottom-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-last-x-value-top-1-snap.png b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-last-x-value-top-1-snap.png index 02cf8fa8c3..e83327a50b 100644 Binary files a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-last-x-value-top-1-snap.png and b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-0-shows-tooltip-on-last-x-value-top-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-90-shows-tooltip-on-first-x-value-bottom-1-snap.png b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-90-shows-tooltip-on-first-x-value-bottom-1-snap.png index d6a11ba864..aa3e2765f0 100644 Binary files a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-90-shows-tooltip-on-first-x-value-bottom-1-snap.png and b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-90-shows-tooltip-on-first-x-value-bottom-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-90-shows-tooltip-on-first-x-value-top-1-snap.png b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-90-shows-tooltip-on-first-x-value-top-1-snap.png index bb87427e82..9aa3c298b8 100644 Binary files a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-90-shows-tooltip-on-first-x-value-top-1-snap.png and b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-rotation-90-shows-tooltip-on-first-x-value-top-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-should-render-corrent-tooltip-for-split-and-y-accessors-1-snap.png b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-should-render-corrent-tooltip-for-split-and-y-accessors-1-snap.png index cade25ac12..042b6fb12a 100644 Binary files a/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-should-render-corrent-tooltip-for-split-and-y-accessors-1-snap.png and b/integration/tests/__image_snapshots__/interactions-test-ts-tooltips-should-render-corrent-tooltip-for-split-and-y-accessors-1-snap.png differ diff --git a/jest.config.js b/jest.config.js index 2137047265..edddf4ce01 100644 --- a/jest.config.js +++ b/jest.config.js @@ -20,7 +20,7 @@ module.exports = { roots: ['/src'], preset: 'ts-jest', testEnvironment: 'jest-environment-jsdom-fourteen', - setupFilesAfterEnv: ['jest-extended', '/scripts/setup_enzyme.ts', '/scripts/custom_matchers.ts'], + setupFilesAfterEnv: ['/scripts/setup_enzyme.ts', '/scripts/custom_matchers.ts'], coveragePathIgnorePatterns: ['/src/mocks/', '/node_modules/'], clearMocks: true, globals: { diff --git a/scripts/custom_matchers.ts b/scripts/custom_matchers.ts index 7a2503b964..92b148e4ae 100644 --- a/scripts/custom_matchers.ts +++ b/scripts/custom_matchers.ts @@ -17,6 +17,7 @@ * under the License. */ import { matcherErrorMessage } from 'jest-matcher-utils'; +import 'jest-extended'; // require to load jest-extended matchers // ensure this is parsed as a module. export {}; diff --git a/src/chart_types/partition_chart/layout/utils/__mocks__/d3_utils.ts b/src/chart_types/partition_chart/layout/utils/__mocks__/d3_utils.ts index 5e11ced284..2953037dcd 100644 --- a/src/chart_types/partition_chart/layout/utils/__mocks__/d3_utils.ts +++ b/src/chart_types/partition_chart/layout/utils/__mocks__/d3_utils.ts @@ -18,6 +18,10 @@ const module = jest.requireActual('../d3_utils.ts'); +export const defaultColor = module.defaultColor; +export const transparentColor = module.transparentColor; +export const defaultD3Color = module.defaultD3Color; + export const stringToRGB = jest.fn(module.stringToRGB); export const validateColor = jest.fn(module.validateColor); export const argsToRGB = jest.fn(module.argsToRGB); diff --git a/src/chart_types/specs.ts b/src/chart_types/specs.ts index 95223d7a30..d411a892a0 100644 --- a/src/chart_types/specs.ts +++ b/src/chart_types/specs.ts @@ -18,12 +18,13 @@ export { AreaSeries, - BarSeries, - LineSeries, Axis, + BarSeries, + BubbleSeries, + HistogramBarSeries, LineAnnotation, + LineSeries, RectAnnotation, - HistogramBarSeries, } from './xy_chart/specs'; export * from './xy_chart/utils/specs'; diff --git a/src/chart_types/xy_chart/annotations/annotation_utils.ts b/src/chart_types/xy_chart/annotations/annotation_utils.ts index e0dceca335..8cf152eeae 100644 --- a/src/chart_types/xy_chart/annotations/annotation_utils.ts +++ b/src/chart_types/xy_chart/annotations/annotation_utils.ts @@ -91,7 +91,7 @@ export function scaleAndValidateDatum(dataValue: any, scale: Scale, alignWithTic const isContinuous = scale.type !== ScaleType.Ordinal; const scaledValue = scale.scale(dataValue); // d3.scale will return 0 for '', rendering the line incorrectly at 0 - if (isNaN(scaledValue) || (isContinuous && dataValue === '')) { + if (scaledValue === null || (isContinuous && dataValue === '')) { return null; } diff --git a/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts b/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts index 4052336d97..e9c72a1b35 100644 --- a/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts +++ b/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts @@ -107,7 +107,7 @@ function computeYDomainLineAnnotationDimensions( const annotationValueYposition = yScale.scale(dataValue); // avoid rendering non scalable annotation values - if (isNaN(annotationValueYposition)) { + if (annotationValueYposition === null) { return; } diff --git a/src/chart_types/xy_chart/crosshair/crosshair_utils.ts b/src/chart_types/xy_chart/crosshair/crosshair_utils.ts index 2195c52409..775b891daf 100644 --- a/src/chart_types/xy_chart/crosshair/crosshair_utils.ts +++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.ts @@ -37,7 +37,7 @@ export function getSnapPosition( totalBarsInCluster = 1, ): { band: number; position: number } | undefined { const position = scale.scale(value); - if (position === undefined) { + if (position === null) { return; } 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 3ce4acd43a..eb689c739f 100644 --- a/src/chart_types/xy_chart/domains/y_domain.test.ts +++ b/src/chart_types/xy_chart/domains/y_domain.test.ts @@ -30,36 +30,31 @@ import { import { GroupId } from '../../../utils/ids'; import { ChartTypes } from '../..'; import { SpecTypes } from '../../../specs/settings'; +import { MockRawDataSeries, MockRawDataSeriesDatum } from '../../../mocks'; describe('Y Domain', () => { test('Should merge Y domain', () => { - const dataSeries: RawDataSeries[] = [ - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + const dataSeries = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 2 }, { x: 2, y1: 2 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], - }, + [ + { x: 1, y1: 2 }, + { x: 4, y1: 7 }, + ], + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ - { x: 1, y1: 2 }, - { x: 4, y1: 7 }, - ], }, - ]; - const specDataSeries = new Map(); + ); + const specDataSeries = new Map(); specDataSeries.set('a', dataSeries); const mergedDomain = mergeYDomain( specDataSeries, @@ -86,48 +81,43 @@ describe('Y Domain', () => { ]); }); test('Should merge Y domain different group', () => { - const dataSeries1: RawDataSeries[] = [ - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + const dataSeries1 = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 2 }, { x: 2, y1: 2 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], - }, - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + [ { x: 1, y1: 2 }, { x: 4, y1: 7 }, ], - }, - ]; - const dataSeries2: RawDataSeries[] = [ + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ + }, + ); + const dataSeries2 = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 10 }, { x: 2, y1: 10 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], + ], + { + specId: 'a', + yAccessor: 'y1', + seriesKeys: [''], + key: '', }, - ]; - const specDataSeries = new Map(); + ); + const specDataSeries = new Map(); specDataSeries.set('a', dataSeries1); specDataSeries.set('b', dataSeries2); const mergedDomain = mergeYDomain( @@ -170,48 +160,44 @@ describe('Y Domain', () => { ]); }); test('Should merge Y domain same group all stacked', () => { - const dataSeries1: RawDataSeries[] = [ - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + const dataSeries1 = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 2 }, { x: 2, y1: 2 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], - }, - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + [ { x: 1, y1: 2 }, { x: 4, y1: 7 }, ], - }, - ]; - const dataSeries2: RawDataSeries[] = [ + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ + }, + ); + + const dataSeries2 = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 10 }, { x: 2, y1: 10 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], + ], + { + specId: 'a', + yAccessor: 'y1', + seriesKeys: [''], + key: '', }, - ]; - const specDataSeries = new Map(); + ); + const specDataSeries = new Map(); specDataSeries.set('a', dataSeries1); specDataSeries.set('b', dataSeries2); const mergedDomain = mergeYDomain( @@ -247,48 +233,43 @@ describe('Y Domain', () => { ]); }); test('Should merge Y domain same group partially stacked', () => { - const dataSeries1: RawDataSeries[] = [ - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ - { x: 1, y1: 2 }, - { x: 2, y1: 2 }, - { x: 3, y1: 2 }, - { x: 4, y1: 5 }, + const dataSeries1 = MockRawDataSeries.fromData( + [ + [ + { x: 1, y1: 2, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 3, y1: 2, mark: null }, + { x: 4, y1: 5, mark: null }, ], - }, + [ + { x: 1, y1: 2, mark: null }, + { x: 4, y1: 7, mark: null }, + ], + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ - { x: 1, y1: 2 }, - { x: 4, y1: 7 }, - ], }, - ]; - const dataSeries2: RawDataSeries[] = [ + ); + const dataSeries2 = MockRawDataSeries.fromData( + [ + [ + { x: 1, y1: 10, mark: null }, + { x: 2, y1: 10, mark: null }, + { x: 3, y1: 2, mark: null }, + { x: 4, y1: 5, mark: null }, + ], + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ - { x: 1, y1: 10 }, - { x: 2, y1: 10 }, - { x: 3, y1: 2 }, - { x: 4, y1: 5 }, - ], }, - ]; - const specDataSeries = new Map(); + ); + const specDataSeries = new Map(); specDataSeries.set('a', dataSeries1); specDataSeries.set('b', dataSeries2); const mergedDomain = mergeYDomain( @@ -324,6 +305,7 @@ describe('Y Domain', () => { }); test('Should merge Y high volume of data', () => { const maxValues = 10000; + const data = new Array(maxValues).fill(0).map((_, i) => MockRawDataSeriesDatum.default({ x: i, y1: i })); const dataSeries1: RawDataSeries[] = [ { specId: 'a', @@ -331,7 +313,7 @@ describe('Y Domain', () => { splitAccessors: new Map(), seriesKeys: [''], key: '', - data: new Array(maxValues).fill(0).map((d, i) => ({ x: i, y1: i })), + data, }, { specId: 'a', @@ -339,7 +321,7 @@ describe('Y Domain', () => { splitAccessors: new Map(), seriesKeys: [''], key: '', - data: new Array(maxValues).fill(0).map((d, i) => ({ x: i, y1: i })), + data, }, ]; const dataSeries2: RawDataSeries[] = [ @@ -349,10 +331,10 @@ describe('Y Domain', () => { splitAccessors: new Map(), seriesKeys: [''], key: '', - data: new Array(maxValues).fill(0).map((d, i) => ({ x: i, y1: i })), + data, }, ]; - const specDataSeries = new Map(); + const specDataSeries = new Map(); specDataSeries.set('a', dataSeries1); specDataSeries.set('b', dataSeries2); const mergedDomain = mergeYDomain( @@ -551,33 +533,27 @@ describe('Y Domain', () => { }); test('Should getDataSeriesOnGroup for matching specs', () => { - const dataSeries: RawDataSeries[] = [ - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + const dataSeries = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 2 }, { x: 2, y1: 2 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], - }, + [ + { x: 1, y1: 2 }, + { x: 4, y1: 7 }, + ], + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ - { x: 1, y1: 2 }, - { x: 4, y1: 7 }, - ], }, - ]; - const specDataSeries = new Map(); + ); + const specDataSeries = new Map(); specDataSeries.set('b', dataSeries); const specs: YBasicSeriesSpec[] = [ @@ -596,34 +572,27 @@ describe('Y Domain', () => { }); test('Should merge Y domain accounting for custom domain limits: complete bounded domain', () => { const groupId = 'a'; - - const dataSeries: RawDataSeries[] = [ - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + const dataSeries = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 2 }, { x: 2, y1: 2 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], - }, + [ + { x: 1, y1: 2 }, + { x: 4, y1: 7 }, + ], + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ - { x: 1, y1: 2 }, - { x: 4, y1: 7 }, - ], }, - ]; - const specDataSeries = new Map(); + ); + const specDataSeries = new Map(); specDataSeries.set('a', dataSeries); const domainsByGroupId = new Map(); domainsByGroupId.set(groupId, { min: 0, max: 20 }); @@ -654,34 +623,27 @@ describe('Y Domain', () => { }); test('Should merge Y domain accounting for custom domain limits: partial lower bounded domain', () => { const groupId = 'a'; - - const dataSeries: RawDataSeries[] = [ - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + const dataSeries = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 2 }, { x: 2, y1: 2 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], - }, + [ + { x: 1, y1: 2 }, + { x: 4, y1: 7 }, + ], + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ - { x: 1, y1: 2 }, - { x: 4, y1: 7 }, - ], }, - ]; - const specDataSeries = new Map(); + ); + const specDataSeries = new Map(); specDataSeries.set('a', dataSeries); const domainsByGroupId = new Map(); domainsByGroupId.set(groupId, { min: 0 }); @@ -712,34 +674,27 @@ describe('Y Domain', () => { }); test('Should not merge Y domain with invalid custom domain limits: partial lower bounded domain', () => { const groupId = 'a'; - - const dataSeries: RawDataSeries[] = [ - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + const dataSeries = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 2 }, { x: 2, y1: 2 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], - }, + [ + { x: 1, y1: 2 }, + { x: 4, y1: 7 }, + ], + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ - { x: 1, y1: 2 }, - { x: 4, y1: 7 }, - ], }, - ]; - const specDataSeries = new Map(); + ); + const specDataSeries = new Map(); specDataSeries.set('a', dataSeries); const domainsByGroupId = new Map(); domainsByGroupId.set(groupId, { min: 20 }); @@ -766,34 +721,27 @@ describe('Y Domain', () => { }); test('Should merge Y domain accounting for custom domain limits: partial upper bounded domain', () => { const groupId = 'a'; - - const dataSeries: RawDataSeries[] = [ - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + const dataSeries = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 2 }, { x: 2, y1: 2 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], - }, + [ + { x: 1, y1: 2 }, + { x: 4, y1: 7 }, + ], + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ - { x: 1, y1: 2 }, - { x: 4, y1: 7 }, - ], }, - ]; - const specDataSeries = new Map(); + ); + const specDataSeries = new Map(); specDataSeries.set('a', dataSeries); const domainsByGroupId = new Map(); domainsByGroupId.set(groupId, { max: 20 }); @@ -824,34 +772,27 @@ describe('Y Domain', () => { }); test('Should not merge Y domain with invalid custom domain limits: partial upper bounded domain', () => { const groupId = 'a'; - - const dataSeries: RawDataSeries[] = [ - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + const dataSeries = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 2 }, { x: 2, y1: 2 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], - }, + [ + { x: 1, y1: 2 }, + { x: 4, y1: 7 }, + ], + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ - { x: 1, y1: 2 }, - { x: 4, y1: 7 }, - ], }, - ]; - const specDataSeries = new Map(); + ); + const specDataSeries = new Map(); specDataSeries.set('a', dataSeries); const domainsByGroupId = new Map(); domainsByGroupId.set(groupId, { max: -1 }); @@ -877,48 +818,43 @@ describe('Y Domain', () => { expect(attemptToMerge).toThrowError(errorMessage); }); test('Should merge Y domain with stacked as percentage', () => { - const dataSeries1: RawDataSeries[] = [ - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + const dataSeries1 = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 2 }, { x: 2, y1: 2 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], - }, - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + [ { x: 1, y1: 2 }, { x: 4, y1: 7 }, ], - }, - ]; - const dataSeries2: RawDataSeries[] = [ + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ + }, + ); + const dataSeries2 = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 10 }, { x: 2, y1: 10 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], + ], + { + specId: 'a', + yAccessor: 'y1', + seriesKeys: [''], + key: '', }, - ]; - const specDataSeries = new Map(); + ); + const specDataSeries = new Map(); specDataSeries.set('a', dataSeries1); specDataSeries.set('b', dataSeries2); const mergedDomain = mergeYDomain( @@ -955,34 +891,27 @@ describe('Y Domain', () => { }); test('Should merge Y domain with as percentage regadless of custom domains', () => { const groupId = 'a'; - - const dataSeries: RawDataSeries[] = [ - { - specId: 'a', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [''], - key: '', - data: [ + const dataSeries = MockRawDataSeries.fromData( + [ + [ { x: 1, y1: 2 }, { x: 2, y1: 2 }, { x: 3, y1: 2 }, { x: 4, y1: 5 }, ], - }, + [ + { x: 1, y1: 2 }, + { x: 4, y1: 7 }, + ], + ], { specId: 'a', yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [''], key: '', - data: [ - { x: 1, y1: 2 }, - { x: 4, y1: 7 }, - ], }, - ]; - const specDataSeries = new Map(); + ); + const specDataSeries = new Map(); specDataSeries.set('a', dataSeries); const domainsByGroupId = new Map(); domainsByGroupId.set(groupId, { min: 2, max: 20 }); diff --git a/src/chart_types/xy_chart/renderer/canvas/areas.ts b/src/chart_types/xy_chart/renderer/canvas/areas.ts index 8ba5236562..48d3517331 100644 --- a/src/chart_types/xy_chart/renderer/canvas/areas.ts +++ b/src/chart_types/xy_chart/renderer/canvas/areas.ts @@ -38,6 +38,7 @@ interface AreaGeometriesProps { export function renderAreas(ctx: CanvasRenderingContext2D, props: AreaGeometriesProps) { withContext(ctx, (ctx) => { const { sharedStyle, highlightedLegendItem, areas, clippings } = props; + withClip(ctx, clippings, (ctx: CanvasRenderingContext2D) => { ctx.save(); @@ -59,16 +60,22 @@ export function renderAreas(ctx: CanvasRenderingContext2D, props: AreaGeometries ctx.clip(); ctx.restore(); }); - for (let i = 0; i < areas.length; i++) { - const glyph = areas[i]; - const { seriesPointStyle, seriesIdentifier } = glyph; + + areas.forEach((area) => { + const { seriesPointStyle, seriesIdentifier } = area; if (seriesPointStyle.visible) { const geometryStateStyle = getGeometryStateStyle(seriesIdentifier, highlightedLegendItem, sharedStyle); - withContext(ctx, () => { - renderPoints(ctx, glyph.points, seriesPointStyle, geometryStateStyle); - }); + withClip( + ctx, + clippings, + (ctx) => { + renderPoints(ctx, area.points, seriesPointStyle, geometryStateStyle); + }, + // TODO: add padding over clipping + area.points[0]?.value.mark !== null, + ); } - } + }); }); } @@ -84,6 +91,7 @@ function renderArea( const fill = buildAreaStyles(color, seriesAreaStyle, geometryStateStyle); renderAreaPath(ctx, transform.x, area, fill, clippedRanges, clippings); } + function renderAreaLines( ctx: CanvasRenderingContext2D, glyph: AreaGeometry, diff --git a/src/chart_types/xy_chart/renderer/canvas/bubbles.ts b/src/chart_types/xy_chart/renderer/canvas/bubbles.ts new file mode 100644 index 0000000000..b118f853fb --- /dev/null +++ b/src/chart_types/xy_chart/renderer/canvas/bubbles.ts @@ -0,0 +1,62 @@ +/* + * 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 { getGeometryStateStyle } from '../../rendering/rendering'; +import { BubbleGeometry, PointGeometry } from '../../../../utils/geometry'; +import { SharedGeometryStateStyle, GeometryStateStyle, PointStyle } from '../../../../utils/themes/theme'; +import { withContext, withClip } from '../../../../renderers/canvas'; +import { renderPointGroup } from './points'; +import { Rect } from '../../../../geoms/types'; +import { LegendItem } from '../../../../commons/legend'; +import { SeriesKey } from '../../../../commons/series_id'; + +interface BubbleGeometriesDataProps { + animated?: boolean; + bubbles: BubbleGeometry[]; + sharedStyle: SharedGeometryStateStyle; + highlightedLegendItem: LegendItem | null; + clippings: Rect; +} + +/** @internal */ +export function renderBubbles(ctx: CanvasRenderingContext2D, props: BubbleGeometriesDataProps) { + withContext(ctx, (ctx) => { + const { bubbles, sharedStyle, highlightedLegendItem, clippings } = 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; + + acc.push(...points); + return acc; + }, []); + + withClip( + ctx, + 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/lines.ts b/src/chart_types/xy_chart/renderer/canvas/lines.ts index 226a8867f1..3ef02c4e57 100644 --- a/src/chart_types/xy_chart/renderer/canvas/lines.ts +++ b/src/chart_types/xy_chart/renderer/canvas/lines.ts @@ -20,7 +20,7 @@ import { getGeometryStateStyle } from '../../rendering/rendering'; import { LineGeometry } from '../../../../utils/geometry'; import { SharedGeometryStateStyle } from '../../../../utils/themes/theme'; import { LegendItem } from '../../../../commons/legend'; -import { withContext } from '../../../../renderers/canvas'; +import { withContext, withClip } from '../../../../renderers/canvas'; import { renderPoints } from './points'; import { renderLinePaths } from './primitives/path'; import { Rect } from '../../../../geoms/types'; @@ -49,10 +49,16 @@ export function renderLines(ctx: CanvasRenderingContext2D, props: LineGeometries } if (seriesPointStyle.visible) { - withContext(ctx, (ctx) => { - const geometryStyle = getGeometryStateStyle(line.seriesIdentifier, highlightedLegendItem, sharedStyle); - renderPoints(ctx, line.points, line.seriesPointStyle, geometryStyle); - }); + withClip( + ctx, + clippings, + (ctx) => { + const geometryStyle = getGeometryStateStyle(line.seriesIdentifier, highlightedLegendItem, sharedStyle); + renderPoints(ctx, line.points, line.seriesPointStyle, geometryStyle); + }, + // TODO: add padding over clipping + line.points[0]?.value.mark !== null, + ); } }); }); diff --git a/src/chart_types/xy_chart/renderer/canvas/points.ts b/src/chart_types/xy_chart/renderer/canvas/points.ts index 39555a099a..9710422789 100644 --- a/src/chart_types/xy_chart/renderer/canvas/points.ts +++ b/src/chart_types/xy_chart/renderer/canvas/points.ts @@ -19,26 +19,82 @@ import { PointGeometry } from '../../../../utils/geometry'; import { PointStyle, GeometryStateStyle } from '../../../../utils/themes/theme'; import { renderCircle } from './primitives/arc'; -import { Circle } from '../../../../geoms/types'; +import { Circle, Stroke, Fill } from '../../../../geoms/types'; import { buildPointStyles } from './styles/point'; +import { SeriesKey } from '../../../../commons/series_id'; -/** @internal */ +/** + * Renders points from single series + * + * @internal + */ export function renderPoints( ctx: CanvasRenderingContext2D, points: PointGeometry[], themeStyle: PointStyle, geometryStateStyle: GeometryStateStyle, ) { - return points.map((point) => { - const { x, y, color, transform, styleOverrides } = point; - const { fill, stroke, radius } = buildPointStyles(color, themeStyle, geometryStateStyle, styleOverrides); + points + .map<[Circle, Fill, Stroke]>((point) => { + const { x, y, color, radius: pointRadius, transform, styleOverrides } = point; + const { fill, stroke, radius } = buildPointStyles( + color, + themeStyle, + geometryStateStyle, + pointRadius, + styleOverrides, + ); + + const circle: Circle = { + x: x + transform.x, + y, + radius, + }; + + return [circle, fill, stroke]; + }) + .sort(([{ radius: a }], [{ radius: b }]) => b - a) + .forEach((args) => renderCircle(ctx, ...args)); +} + +/** + * Renders points in group from multiple series on a single layer + * + * @internal + */ +export function renderPointGroup( + ctx: CanvasRenderingContext2D, + points: PointGeometry[], + themeStyles: Record, + geometryStateStyles: Record, +) { + points + .map<[Circle, Fill, Stroke]>((point) => { + const { + x, + y, + color, + radius: pointRadius, + transform, + styleOverrides, + seriesIdentifier: { key }, + } = point; + const { fill, stroke, radius } = buildPointStyles( + color, + themeStyles[key], + geometryStateStyles[key], + pointRadius, + styleOverrides, + ); - const circle: Circle = { - x: x + transform.x, - y, - radius, - }; + const circle: Circle = { + x: x + transform.x, + y, + radius, + }; - renderCircle(ctx, circle, fill, stroke); - }); + return [circle, fill, stroke]; + }) + .sort(([{ radius: a }], [{ radius: b }]) => b - a) + .forEach((args) => renderCircle(ctx, ...args)); } diff --git a/src/chart_types/xy_chart/renderer/canvas/renderers.ts b/src/chart_types/xy_chart/renderer/canvas/renderers.ts index aa117c5284..1e6d701613 100644 --- a/src/chart_types/xy_chart/renderer/canvas/renderers.ts +++ b/src/chart_types/xy_chart/renderer/canvas/renderers.ts @@ -28,6 +28,7 @@ import { renderBarValues } from './values/bar'; import { renderDebugRect } from './utils/debug'; import { stringToRGB } from '../../../partition_chart/layout/utils/d3_utils'; import { Rect } from '../../../../geoms/types'; +import { renderBubbles } from './bubbles'; /** @internal */ export function renderXYChartCanvas2d( @@ -44,6 +45,7 @@ export function renderXYChartCanvas2d( chartTransform, chartRotation, geometries, + geometriesIndex, theme, highlightedLegendItem, annotationDimensions, @@ -101,7 +103,7 @@ export function renderXYChartCanvas2d( }); }, - // rendering bars/areas/lines + // rendering bars (ctx: CanvasRenderingContext2D) => { withContext(ctx, (ctx) => { ctx.translate(transform.x, transform.y); @@ -109,6 +111,7 @@ export function renderXYChartCanvas2d( renderBars(ctx, geometries.bars, theme.sharedStyle, clippings, highlightedLegendItem); }); }, + // rendering areas (ctx: CanvasRenderingContext2D) => { withContext(ctx, (ctx) => { ctx.translate(transform.x, transform.y); @@ -121,6 +124,7 @@ export function renderXYChartCanvas2d( }); }); }, + // rendering lines (ctx: CanvasRenderingContext2D) => { withContext(ctx, (ctx) => { ctx.translate(transform.x, transform.y); @@ -133,6 +137,19 @@ export function renderXYChartCanvas2d( }); }); }, + // 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, + }); + }); + }, (ctx: CanvasRenderingContext2D) => { withContext(ctx, (ctx) => { ctx.translate(transform.x, transform.y); @@ -161,18 +178,21 @@ export function renderXYChartCanvas2d( ); }); }, + // rendering debugger (ctx: CanvasRenderingContext2D) => { if (!debug) { return; } withContext(ctx, (ctx) => { + const { left, top, width, height } = chartDimensions; + renderDebugRect( ctx, { - x: chartDimensions.left, - y: chartDimensions.top, - width: chartDimensions.width, - height: chartDimensions.height, + x: left, + y: top, + width, + height, }, { color: stringToRGB('transparent'), @@ -183,6 +203,18 @@ export function renderXYChartCanvas2d( dash: [4, 4], }, ); + + const triangulation = geometriesIndex.triangulation([0, 0, width, height]); + + if (triangulation) { + ctx.beginPath(); + ctx.translate(left, top); + ctx.setLineDash([5, 5]); + triangulation.render(ctx); + ctx.lineWidth = 1; + ctx.strokeStyle = 'blue'; + ctx.stroke(); + } }); }, ]); diff --git a/src/chart_types/xy_chart/renderer/canvas/styles/point.test.ts b/src/chart_types/xy_chart/renderer/canvas/styles/point.test.ts index 5cb71d12de..c7f2fd32b8 100644 --- a/src/chart_types/xy_chart/renderer/canvas/styles/point.test.ts +++ b/src/chart_types/xy_chart/renderer/canvas/styles/point.test.ts @@ -34,6 +34,7 @@ describe('Point styles', () => { let baseColor = COLOR; let themePointStyle = MockStyles.point(); let geometryStateStyle = MockStyles.geometryState(); + const pointRadius = 0; let overrides: Partial = {}; function setDefaults() { @@ -44,7 +45,7 @@ describe('Point styles', () => { } beforeEach(() => { - result = buildPointStyles(baseColor, themePointStyle, geometryStateStyle, overrides); + result = buildPointStyles(baseColor, themePointStyle, geometryStateStyle, pointRadius, overrides); }); it('should call getColorFromVariant with correct args for fill', () => { @@ -59,8 +60,20 @@ describe('Point styles', () => { expect(result.stroke.width).toBe(themePointStyle.strokeWidth); }); - it('should set radius from themePointStyle', () => { - expect(result.radius).toBe(themePointStyle.radius); + describe('Radius', () => { + it('should set radius with themePointStyle, from max value', () => { + expect(result.radius).toBe(themePointStyle.radius); + }); + + it('should set radius with pointRadius, from max value', () => { + result = buildPointStyles(baseColor, themePointStyle, geometryStateStyle, 100, overrides); + expect(result.radius).toBe(100); + }); + + it('should set radius with override', () => { + result = buildPointStyles(baseColor, themePointStyle, geometryStateStyle, 100, { radius: 21 }); + expect(result.radius).toBe(21); + }); }); describe('Colors', () => { diff --git a/src/chart_types/xy_chart/renderer/canvas/styles/point.ts b/src/chart_types/xy_chart/renderer/canvas/styles/point.ts index 775ed077ad..9fc49382de 100644 --- a/src/chart_types/xy_chart/renderer/canvas/styles/point.ts +++ b/src/chart_types/xy_chart/renderer/canvas/styles/point.ts @@ -35,6 +35,7 @@ export function buildPointStyles( baseColor: string, themePointStyle: PointStyle, geometryStateStyle: GeometryStateStyle, + pointRadius: number, overrides?: Partial, ): { fill: Fill; stroke: Stroke; radius: number } { const pointStyle = mergePartial(themePointStyle, overrides); @@ -51,6 +52,15 @@ export function buildPointStyles( width: pointStyle.strokeWidth, }; - const radius = overrides && overrides.radius ? overrides.radius : themePointStyle.radius; + const radius = getRadius(pointRadius, themePointStyle.radius, overrides?.radius); return { fill, stroke, radius }; } + +/** @internal */ +export function getRadius(pointRadius: number, themeRadius: number, overrideRadius?: number) { + if (overrideRadius !== undefined) { + return overrideRadius; + } + + return Math.max(pointRadius, themeRadius); +} diff --git a/src/chart_types/xy_chart/renderer/canvas/styles/points.test.ts b/src/chart_types/xy_chart/renderer/canvas/styles/points.test.ts new file mode 100644 index 0000000000..65c7426fcc --- /dev/null +++ b/src/chart_types/xy_chart/renderer/canvas/styles/points.test.ts @@ -0,0 +1,39 @@ +/* + * 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 { getRadius } from './point'; + +describe('point', () => { + describe('#getRadius', () => { + it('should return max of point and theme radius - theme', () => { + expect(getRadius(10, 20)).toBe(20); + }); + + it('should return max of point and theme radius - point', () => { + expect(getRadius(30, 20)).toBe(30); + }); + + it('should return override if provided - lower', () => { + expect(getRadius(10, 20, 5)).toBe(5); + }); + + it('should return override if provided - higher', () => { + expect(getRadius(10, 20, 50)).toBe(50); + }); + }); +}); 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 d18fe2bc77..e20876f73e 100644 --- a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx +++ b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx @@ -47,6 +47,7 @@ import { renderXYChartCanvas2d } from './renderers'; import { isChartEmptySelector } from '../../state/selectors/is_chart_empty'; import { deepEqual } from '../../../../utils/fast_deep_equal'; import { Rotation } from '../../../../utils/commons'; +import { IndexedGeometryMap } from '../../utils/indexed_geometry_map'; /** @internal */ export interface ReactiveChartStateProps { @@ -54,6 +55,7 @@ export interface ReactiveChartStateProps { debug: boolean; isChartEmpty: boolean; geometries: Geometries; + geometriesIndex: IndexedGeometryMap; theme: Theme; chartContainerDimensions: Dimensions; chartRotation: Rotation; @@ -180,7 +182,9 @@ const DEFAULT_PROPS: ReactiveChartStateProps = { bars: [], lines: [], points: [], + bubbles: [], }, + geometriesIndex: new IndexedGeometryMap(), theme: LIGHT_THEME, chartContainerDimensions: { width: 0, @@ -218,11 +222,15 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { if (!isInitialized(state)) { return DEFAULT_PROPS; } + + const { geometries, geometriesIndex } = computeSeriesGeometriesSelector(state); + return { initialized: true, isChartEmpty: isChartEmptySelector(state), debug: getSettingsSpecSelector(state).debug, - geometries: computeSeriesGeometriesSelector(state).geometries, + geometries, + geometriesIndex, theme: getChartThemeSelector(state), chartContainerDimensions: getChartContainerDimensionsSelector(state), highlightedLegendItem: getHighlightedSeriesSelector(state), diff --git a/src/chart_types/xy_chart/renderer/dom/highlighter.tsx b/src/chart_types/xy_chart/renderer/dom/highlighter.tsx index 7537d76cfa..2ff2f7a204 100644 --- a/src/chart_types/xy_chart/renderer/dom/highlighter.tsx +++ b/src/chart_types/xy_chart/renderer/dom/highlighter.tsx @@ -28,6 +28,7 @@ import { Rotation } from '../../../../utils/commons'; import { Transform } from '../../state/utils'; import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation'; import { computeChartDimensionsSelector } from '../../state/selectors/compute_chart_dimensions'; +import { DEFAULT_HIGHLIGHT_PADDING } from '../../rendering/rendering'; interface HighlighterProps { initialized: boolean; @@ -64,10 +65,11 @@ class HighlighterComponent extends React.Component { key={i} cx={x + geom.transform.x} cy={y} - r={geom.radius} + r={geom.radius + DEFAULT_HIGHLIGHT_PADDING} stroke={color} strokeWidth={4} fill="transparent" + clipPath={geom.value.mark !== null ? `url(#${clipPathId})` : undefined} /> ); } 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 6c9180a3ea..9597b680c1 100644 --- a/src/chart_types/xy_chart/rendering/rendering.areas.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.areas.test.ts @@ -18,7 +18,7 @@ import { ScaleType } from '../../../scales'; import { CurveType } from '../../../utils/curves'; -import { IndexedGeometry, PointGeometry, AreaGeometry } from '../../../utils/geometry'; +import { PointGeometry, AreaGeometry } from '../../../utils/geometry'; import { LIGHT_THEME } from '../../../utils/themes/light_theme'; import { computeXScale, computeYScales } from '../utils/scales'; import { AreaSeriesSpec, SeriesTypes } from '../utils/specs'; @@ -27,6 +27,7 @@ import { renderArea } from './rendering'; import { ChartTypes } from '../..'; import { SpecTypes } from '../../../specs/settings'; import { MockSeriesSpec } from '../../../mocks/specs'; +import { IndexedGeometryMap } from '../utils/indexed_geometry_map'; const SPEC_ID = 'spec_1'; const GROUP_ID = 'group_1'; @@ -62,7 +63,7 @@ describe('Rendering points - areas', () => { }); let renderedArea: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -76,6 +77,9 @@ describe('Rendering points - areas', () => { false, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); }); test('Render geometry but empty upper and lower lines and area paths', () => { @@ -117,7 +121,7 @@ describe('Rendering points - areas', () => { const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedArea: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -131,6 +135,9 @@ describe('Rendering points - areas', () => { false, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); }); test('Can render an line and area paths', () => { @@ -148,13 +155,13 @@ describe('Rendering points - areas', () => { test('Can render two points', () => { const { areaGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = renderedArea; expect(points[0]).toEqual(({ x: 0, y: 0, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -168,6 +175,7 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 0, y: 10, + mark: null, }, transform: { x: 25, @@ -177,7 +185,7 @@ describe('Rendering points - areas', () => { expect(points[1]).toEqual(({ x: 50, y: 50, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -191,13 +199,14 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 1, y: 5, + mark: null, }, transform: { x: 25, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); }); describe('Multi series area chart - ordinal', () => { @@ -246,11 +255,11 @@ describe('Rendering points - areas', () => { let firstLine: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; let secondLine: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -264,6 +273,9 @@ describe('Rendering points - areas', () => { false, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); secondLine = renderArea( 25, // adding a ideal 25px shift, generally applied by renderGeometries @@ -275,6 +287,9 @@ describe('Rendering points - areas', () => { false, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); }); @@ -296,13 +311,13 @@ describe('Rendering points - areas', () => { test('can render first spec points', () => { const { areaGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = firstLine; expect(points.length).toEqual(2); expect(points[0]).toEqual(({ x: 0, y: 50, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: spec1Id, @@ -316,6 +331,7 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 0, y: 10, + mark: null, }, transform: { x: 25, @@ -325,7 +341,7 @@ describe('Rendering points - areas', () => { expect(points[1]).toEqual(({ x: 50, y: 75, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: spec1Id, @@ -339,24 +355,25 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 1, y: 5, + mark: null, }, transform: { x: 25, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); test('can render second spec points', () => { const { areaGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = secondLine; expect(points.length).toEqual(2); expect(points[0]).toEqual(({ x: 0, y: 0, - radius: 10, + radius: 0, color: 'blue', seriesIdentifier: { specId: spec2Id, @@ -370,6 +387,7 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 0, y: 20, + mark: null, }, transform: { x: 25, @@ -379,7 +397,7 @@ describe('Rendering points - areas', () => { expect(points[1]).toEqual(({ x: 50, y: 50, - radius: 10, + radius: 0, color: 'blue', seriesIdentifier: { specId: spec2Id, @@ -393,13 +411,14 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 1, y: 10, + mark: null, }, transform: { x: 25, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); }); describe('Single series area chart - linear', () => { @@ -430,7 +449,7 @@ describe('Rendering points - areas', () => { let renderedArea: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -444,6 +463,9 @@ describe('Rendering points - areas', () => { false, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); }); test('Can render a linear area', () => { @@ -457,12 +479,12 @@ describe('Rendering points - areas', () => { test('Can render two points', () => { const { areaGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = renderedArea; expect(points[0]).toEqual(({ x: 0, y: 0, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -476,6 +498,7 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 0, y: 10, + mark: null, }, transform: { x: 0, @@ -485,7 +508,7 @@ describe('Rendering points - areas', () => { expect(points[1]).toEqual(({ x: 100, y: 50, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -499,13 +522,14 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 1, y: 5, + mark: null, }, transform: { x: 0, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); }); describe('Multi series area chart - linear', () => { @@ -554,11 +578,11 @@ describe('Rendering points - areas', () => { let firstLine: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; let secondLine: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -572,6 +596,9 @@ describe('Rendering points - areas', () => { false, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); secondLine = renderArea( 0, // not applied any shift, renderGeometries applies it only with mixed charts @@ -583,6 +610,9 @@ describe('Rendering points - areas', () => { false, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); }); test('can render two linear areas', () => { @@ -603,13 +633,13 @@ describe('Rendering points - areas', () => { test('can render first spec points', () => { const { areaGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = firstLine; expect(points.length).toEqual(2); expect(points[0]).toEqual(({ x: 0, y: 50, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: spec1Id, @@ -623,6 +653,7 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 0, y: 10, + mark: null, }, transform: { x: 0, @@ -632,7 +663,7 @@ describe('Rendering points - areas', () => { expect(points[1]).toEqual(({ x: 100, y: 75, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: spec1Id, @@ -646,24 +677,25 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 1, y: 5, + mark: null, }, transform: { x: 0, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); test('can render second spec points', () => { const { areaGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = secondLine; expect(points.length).toEqual(2); expect(points[0]).toEqual(({ x: 0, y: 0, - radius: 10, + radius: 0, color: 'blue', seriesIdentifier: { specId: spec2Id, @@ -677,6 +709,7 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 0, y: 20, + mark: null, }, transform: { x: 0, @@ -686,7 +719,7 @@ describe('Rendering points - areas', () => { expect(points[1]).toEqual(({ x: 100, y: 50, - radius: 10, + radius: 0, color: 'blue', seriesIdentifier: { specId: spec2Id, @@ -700,13 +733,14 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 1, y: 10, + mark: null, }, transform: { x: 0, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); }); describe('Single series area chart - time', () => { @@ -737,7 +771,7 @@ describe('Rendering points - areas', () => { let renderedArea: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -751,6 +785,9 @@ describe('Rendering points - areas', () => { false, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); }); test('Can render a time area', () => { @@ -764,12 +801,12 @@ describe('Rendering points - areas', () => { test('Can render two points', () => { const { areaGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = renderedArea; expect(points[0]).toEqual(({ x: 0, y: 0, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -783,6 +820,7 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 1546300800000, y: 10, + mark: null, }, transform: { x: 0, @@ -792,7 +830,7 @@ describe('Rendering points - areas', () => { expect(points[1]).toEqual(({ x: 100, y: 50, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -806,13 +844,14 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 1546387200000, y: 5, + mark: null, }, transform: { x: 0, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); }); describe('Multi series area chart - time', () => { @@ -861,11 +900,11 @@ describe('Rendering points - areas', () => { let firstLine: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; let secondLine: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -879,6 +918,9 @@ describe('Rendering points - areas', () => { false, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); secondLine = renderArea( 0, // not applied any shift, renderGeometries applies it only with mixed charts @@ -890,18 +932,21 @@ describe('Rendering points - areas', () => { false, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); }); test('can render first spec points', () => { const { areaGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = firstLine; expect(points.length).toEqual(2); expect(points[0]).toEqual(({ x: 0, y: 50, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: spec1Id, @@ -915,6 +960,7 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 1546300800000, y: 10, + mark: null, }, transform: { x: 0, @@ -924,7 +970,7 @@ describe('Rendering points - areas', () => { expect(points[1]).toEqual(({ x: 100, y: 75, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: spec1Id, @@ -938,24 +984,25 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 1546387200000, y: 5, + mark: null, }, transform: { x: 0, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); test('can render second spec points', () => { const { areaGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = secondLine; expect(points.length).toEqual(2); expect(points[0]).toEqual(({ x: 0, y: 0, - radius: 10, + radius: 0, color: 'blue', seriesIdentifier: { specId: spec2Id, @@ -969,6 +1016,7 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 1546300800000, y: 20, + mark: null, }, transform: { x: 0, @@ -978,7 +1026,7 @@ describe('Rendering points - areas', () => { expect(points[1]).toEqual(({ x: 100, y: 50, - radius: 10, + radius: 0, color: 'blue', seriesIdentifier: { specId: spec2Id, @@ -992,13 +1040,14 @@ describe('Rendering points - areas', () => { accessor: 'y1', x: 1546387200000, y: 10, + mark: null, }, transform: { x: 0, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); }); describe('Single series area chart - y log', () => { @@ -1036,7 +1085,7 @@ describe('Rendering points - areas', () => { let renderedArea: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -1050,6 +1099,9 @@ describe('Rendering points - areas', () => { false, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); }); test('Can render a splitted area and line', () => { @@ -1064,16 +1116,16 @@ describe('Rendering points - areas', () => { test('Can render points', () => { const { areaGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = renderedArea; // all the points minus the undefined ones on a log scale expect(points.length).toBe(7); // all the points expect null geometries - expect(indexedGeometries.size).toEqual(8); - const nullIndexdGeometry = indexedGeometries.get(2)!; - expect(nullIndexdGeometry).toBeUndefined(); + expect(indexedGeometryMap.size).toEqual(8); + const nullIndexdGeometry = indexedGeometryMap.find(2)!; + expect(nullIndexdGeometry).toEqual([]); - const zeroValueIndexdGeometry = indexedGeometries.get(5)!; + const zeroValueIndexdGeometry = indexedGeometryMap.find(5)!; expect(zeroValueIndexdGeometry).toBeDefined(); expect(zeroValueIndexdGeometry.length).toBe(1); // moved to the bottom of the chart @@ -1118,6 +1170,7 @@ describe('Rendering points - areas', () => { x: 1546300800000, y0: null, y1: 0, + mark: null, }, { datum: [1546387200000, 5], @@ -1126,6 +1179,7 @@ describe('Rendering points - areas', () => { x: 1546387200000, y0: null, y1: 0.7142857142857143, + mark: null, }, ]); }); @@ -1163,6 +1217,7 @@ describe('Rendering points - areas', () => { x: 1546300800000, y0: null, y1: null, + mark: null, }, { datum: [1546387200000, 5], @@ -1171,6 +1226,7 @@ describe('Rendering points - areas', () => { x: 1546387200000, y0: null, y1: 5, + mark: null, }, ]); @@ -1182,6 +1238,7 @@ describe('Rendering points - areas', () => { x: 1546300800000, y0: null, y1: 3, + mark: null, }, { datum: [1546387200000, null], @@ -1190,7 +1247,96 @@ describe('Rendering points - areas', () => { x: 1546387200000, y0: null, y1: null, + mark: null, }, ]); }); + + describe('Error guards for scaled values', () => { + const pointSeriesSpec: AreaSeriesSpec = { + chartType: ChartTypes.XYAxis, + specType: SpecTypes.Series, + id: SPEC_ID, + groupId: GROUP_ID, + seriesType: SeriesTypes.Area, + yScaleToDataExtent: false, + 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 0a79326977..2aaad72a22 100644 --- a/src/chart_types/xy_chart/rendering/rendering.bands.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.bands.test.ts @@ -23,9 +23,11 @@ import { renderArea, renderBars } from './rendering'; import { computeXScale, computeYScales } from '../utils/scales'; import { AreaSeriesSpec, BarSeriesSpec, SeriesTypes } from '../utils/specs'; import { LIGHT_THEME } from '../../../utils/themes/light_theme'; -import { AreaGeometry, IndexedGeometry, PointGeometry } from '../../../utils/geometry'; +import { AreaGeometry, PointGeometry } from '../../../utils/geometry'; import { ChartTypes } from '../..'; import { SpecTypes } from '../../../specs/settings'; +import { IndexedGeometryMap } from '../utils/indexed_geometry_map'; +import { MockPointGeometry } from '../../../mocks'; const SPEC_ID = 'spec_1'; const GROUP_ID = 'group_1'; @@ -59,7 +61,7 @@ describe('Rendering bands - areas', () => { const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedArea: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -73,6 +75,9 @@ describe('Rendering bands - areas', () => { true, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); }); test('Render geometry but empty upper and lower lines and area paths', () => { @@ -115,7 +120,7 @@ describe('Rendering bands - areas', () => { const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedArea: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -129,6 +134,9 @@ describe('Rendering bands - areas', () => { true, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); }); test('Can render upper and lower lines and area paths', () => { @@ -153,7 +161,7 @@ describe('Rendering bands - areas', () => { expect(points[0]).toEqual(({ x: 0, y: 80, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -167,6 +175,7 @@ describe('Rendering bands - areas', () => { accessor: 'y0', x: 0, y: 2, + mark: null, }, transform: { x: 25, @@ -177,7 +186,7 @@ describe('Rendering bands - areas', () => { expect(points[1]).toEqual(({ x: 0, y: 0, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -191,6 +200,7 @@ describe('Rendering bands - areas', () => { accessor: 'y1', x: 0, y: 10, + mark: null, }, transform: { x: 25, @@ -200,7 +210,7 @@ describe('Rendering bands - areas', () => { expect(points[2]).toEqual(({ x: 50, y: 70, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -213,6 +223,7 @@ describe('Rendering bands - areas', () => { accessor: 'y0', x: 1, y: 3, + mark: null, }, styleOverrides: undefined, transform: { @@ -223,7 +234,7 @@ describe('Rendering bands - areas', () => { expect(points[3]).toEqual(({ x: 50, y: 50, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -237,6 +248,7 @@ describe('Rendering bands - areas', () => { accessor: 'y1', x: 1, y: 5, + mark: null, }, transform: { x: 25, @@ -275,7 +287,7 @@ describe('Rendering bands - areas', () => { const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedArea: { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -289,6 +301,9 @@ describe('Rendering bands - areas', () => { true, 0, LIGHT_THEME.areaSeriesStyle, + { + enabled: false, + }, ); }); test('Can render upper and lower lines and area paths', () => { @@ -309,141 +324,96 @@ describe('Rendering bands - areas', () => { const { areaGeometry: { points }, } = renderedArea; - // expect(points).toBe(6); expect(points.length).toBe(6); - expect(points[0]).toEqual(({ - x: 0, - y: 80, - radius: 10, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', - }, - value: { - accessor: 'y0', + const getPointGeo = MockPointGeometry.fromBaseline( + { x: 0, - y: 2, - }, - transform: { - x: 25, y: 0, + radius: 0, + color: 'red', + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + }, + transform: { + x: 25, + y: 0, + }, }, - } as unknown) as PointGeometry); - - expect(points[1]).toEqual(({ - x: 0, - y: 0, - radius: 10, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', - }, - value: { - accessor: 'y1', + 'seriesIdentifier', + ); + expect(points[0]).toMatchObject( + getPointGeo({ x: 0, - y: 10, - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[2]).toEqual(({ - x: 50, - y: 70, - radius: 10, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', - }, - value: { - accessor: 'y0', - x: 2, - y: 3, - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[3]).toEqual(({ - x: 50, - y: 50, - radius: 10, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', - }, - value: { - accessor: 'y1', - x: 2, - y: 5, - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[4]).toEqual(({ - x: 75, - y: 70, - radius: 10, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', - }, - value: { - accessor: 'y0', - x: 3, - y: 3, - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); - expect(points[5]).toEqual(({ - x: 75, - y: 50, - radius: 10, - color: 'red', - seriesIdentifier: { - specId: SPEC_ID, - yAccessor: 2, - splitAccessors: new Map(), - seriesKeys: [2], - key: 'spec{spec_1}yAccessor{2}splitAccessors{}', - }, - value: { - accessor: 'y1', - x: 3, - y: 5, - }, - transform: { - x: 25, - y: 0, - }, - } as unknown) as PointGeometry); + y: 80, + value: { + accessor: 'y0', + x: 0, + y: 2, + mark: null, + }, + }), + ); + expect(points[1]).toMatchObject( + getPointGeo({ + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + }, + }), + ); + expect(points[2]).toMatchObject( + getPointGeo({ + x: 50, + y: 70, + value: { + accessor: 'y0', + x: 2, + y: 3, + mark: null, + }, + }), + ); + expect(points[3]).toMatchObject( + getPointGeo({ + x: 50, + y: 50, + value: { + accessor: 'y1', + x: 2, + y: 5, + mark: null, + }, + }), + ); + expect(points[4]).toMatchObject( + getPointGeo({ + x: 75, + y: 70, + value: { + accessor: 'y0', + x: 3, + y: 3, + mark: null, + }, + }), + ); + expect(points[5]).toMatchObject( + getPointGeo({ + x: 75, + y: 50, + value: { + accessor: 'y1', + x: 3, + y: 5, + mark: null, + }, + }), + ); }); }); describe('Single series band bar chart - ordinal', () => { @@ -495,6 +465,7 @@ describe('Rendering bands - areas', () => { accessor: 'y1', x: 0, y: 10, + mark: null, }, seriesIdentifier: { specId: SPEC_ID, @@ -533,6 +504,7 @@ describe('Rendering bands - areas', () => { accessor: 'y1', x: 2, y: 5, + mark: null, }, seriesIdentifier: { specId: SPEC_ID, @@ -571,6 +543,7 @@ describe('Rendering bands - areas', () => { accessor: 'y1', x: 3, y: 8, + mark: null, }, seriesIdentifier: { specId: SPEC_ID, 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 11b60725ae..d08b7597c2 100644 --- a/src/chart_types/xy_chart/rendering/rendering.bars.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.bars.test.ts @@ -26,6 +26,7 @@ import { LIGHT_THEME } from '../../../utils/themes/light_theme'; import { GroupId } from '../../../utils/ids'; import { ChartTypes } from '../..'; import { SpecTypes } from '../../../specs/settings'; +import { MockBarGeometry } from '../../../mocks'; const SPEC_ID = 'spec_1'; const GROUP_ID = 'group_1'; @@ -69,82 +70,42 @@ describe('Rendering bars', () => { LIGHT_THEME.barSeriesStyle, ); - expect(barGeometries[0]).toEqual({ - x: 0, - y: 0, - width: 50, - height: 100, - color: 'red', - value: { - accessor: 'y1', + const getBarGeometry = MockBarGeometry.fromBaseline( + { x: 0, - y: 10, - }, - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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: 50, - height: 50, - color: 'red', - value: { - accessor: 'y1', - x: 1, - y: 5, - }, - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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, - }, - }, - }); + 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, + }, + }), + ); expect(barGeometries.length).toBe(2); }); test('Can render bars with value labels', () => { @@ -246,27 +207,18 @@ describe('Rendering bars', () => { 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, - ); - expect(barGeometries.length).toEqual(2); - expect(barGeometries[0]).toEqual({ + const getBarGeometry = MockBarGeometry.fromBaseline( + { x: 0, - y: 50, - width: 25, - height: 50, + y: 0, + width: 50, + height: 100, color: 'red', value: { accessor: 'y1', x: 0, y: 10, + mark: null, }, seriesIdentifier: { specId: spec1Id, @@ -275,64 +227,44 @@ describe('Rendering bars', () => { splitAccessors: new Map(), seriesKeys: [1], }, - 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: 75, - width: 25, - height: 25, - color: 'red', - value: { - accessor: 'y1', - x: 1, - y: 5, - }, - seriesIdentifier: { - specId: spec1Id, - key: 'spec{bar1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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, - }, - }, - }); + }, + '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( @@ -343,83 +275,53 @@ describe('Rendering bars', () => { 'blue', LIGHT_THEME.barSeriesStyle, ); - expect(barGeometries.length).toEqual(2); - expect(barGeometries[0]).toEqual({ - x: 25, - y: 0, - width: 25, - height: 100, - color: 'blue', - value: { - accessor: 'y1', + const getBarGeometry = MockBarGeometry.fromBaseline( + { x: 0, - y: 20, - }, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{bar2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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: 75, - y: 50, - width: 25, - height: 50, - color: 'blue', - value: { - accessor: 'y1', - x: 1, - y: 10, - }, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{bar2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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, - }, - }, - }); + 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', () => { @@ -457,82 +359,42 @@ describe('Rendering bars', () => { 'red', LIGHT_THEME.barSeriesStyle, ); - expect(barGeometries[0]).toEqual({ - x: 0, - y: 0, - width: 50, - height: 100, - color: 'red', - value: { - accessor: 'y1', + const getBarGeometry = MockBarGeometry.fromBaseline( + { x: 0, - y: 10, - }, - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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: 50, - height: 50, - color: 'red', - value: { - accessor: 'y1', - x: 1, - y: 5, - }, - seriesIdentifier: { - specId: SPEC_ID, - key: 'spec{spec_1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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, - }, - }, - }); + 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', () => { @@ -633,83 +495,42 @@ describe('Rendering bars', () => { 'red', LIGHT_THEME.barSeriesStyle, ); - expect(barGeometries.length).toEqual(2); - expect(barGeometries[0]).toEqual({ - x: 0, - y: 50, - width: 25, - height: 50, - color: 'red', - value: { - accessor: 'y1', + const getBarGeometry = MockBarGeometry.fromBaseline( + { x: 0, - y: 10, - }, - seriesIdentifier: { - specId: spec1Id, - key: 'spec{bar1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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: 75, - width: 25, - height: 25, - color: 'red', - value: { - accessor: 'y1', - x: 1, - y: 5, - }, - seriesIdentifier: { - specId: spec1Id, - key: 'spec{bar1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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, - }, - }, - }); + 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( @@ -720,83 +541,43 @@ describe('Rendering bars', () => { '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({ - 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: 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: 75, - y: 50, - width: 25, - height: 50, - color: 'blue', - value: { - accessor: 'y1', - x: 1, - y: 10, - }, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{bar2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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[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', () => { @@ -852,83 +633,44 @@ describe('Rendering bars', () => { '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({ - 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: 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: 75, - width: 25, - height: 25, - color: 'red', - value: { - accessor: 'y1', - x: 1546387200000, - y: 5, - }, - seriesIdentifier: { - specId: spec1Id, - key: 'spec{bar1}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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[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( @@ -939,83 +681,46 @@ describe('Rendering bars', () => { '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({ - x: 25, - y: 0, - width: 25, - height: 100, - color: 'blue', - value: { - accessor: 'y1', - x: 1546300800000, - y: 20, - }, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{bar2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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: 75, - y: 50, - width: 25, - height: 50, - color: 'blue', - value: { - accessor: 'y1', - x: 1546387200000, - y: 10, - }, - seriesIdentifier: { - specId: spec2Id, - key: 'spec{bar2}yAccessor{1}splitAccessors{}', - yAccessor: 1, - splitAccessors: new Map(), - seriesKeys: [1], - }, - 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[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', () => { @@ -1052,7 +757,7 @@ describe('Rendering bars', () => { const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); test('Can render 3 bars', () => { - const { barGeometries, indexedGeometries } = renderBars( + const { barGeometries, indexedGeometryMap } = renderBars( 0, barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0], xScale, @@ -1063,7 +768,7 @@ describe('Rendering bars', () => { expect(barGeometries.length).toBe(3); // will be cut by the clipping areas in the rendering component expect(barGeometries[2].height).toBe(1000); - expect(indexedGeometries.size).toBe(3); + expect(indexedGeometryMap.size).toBe(3); }); }); describe('Renders minBarHeight', () => { diff --git a/src/chart_types/xy_chart/rendering/rendering.bubble.test.ts b/src/chart_types/xy_chart/rendering/rendering.bubble.test.ts new file mode 100644 index 0000000000..1e7c910c9f --- /dev/null +++ b/src/chart_types/xy_chart/rendering/rendering.bubble.test.ts @@ -0,0 +1,1271 @@ +/* + * 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 { computeSeriesDomains } from '../state/utils'; +import { ScaleType } from '../../../scales'; +import { renderBubble } from './rendering'; +import { computeXScale, computeYScales } from '../utils/scales'; +import { BubbleSeriesSpec, DomainRange, SeriesTypes } from '../utils/specs'; +import { LIGHT_THEME } from '../../../utils/themes/light_theme'; +import { BubbleGeometry, PointGeometry } from '../../../utils/geometry'; +import { GroupId } from '../../../utils/ids'; +import { ChartTypes } from '../..'; +import { SpecTypes } from '../../../specs/settings'; +import { IndexedGeometryMap } from '../utils/indexed_geometry_map'; + +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, + yScaleToDataExtent: false, + 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, + id: SPEC_ID, + groupId: GROUP_ID, + seriesType: SeriesTypes.Bubble, + yScaleToDataExtent: false, + 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, + ); + }); + test('Can render a 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); + }); + 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], + }, + styleOverrides: undefined, + value: { + accessor: 'y1', + x: 0, + y: 10, + mark: null, + }, + 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, + }, + transform: { + x: 25, + y: 0, + }, + } as unknown) as PointGeometry); + expect(indexedGeometryMap.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, + id: spec1Id, + groupId: GROUP_ID, + seriesType: SeriesTypes.Bubble, + yScaleToDataExtent: false, + data: [ + [0, 10], + [1, 5], + ], + xAccessor: 0, + yAccessors: [1], + xScaleType: ScaleType.Ordinal, + yScaleType: ScaleType.Linear, + }; + const pointSeriesSpec2: BubbleSeriesSpec = { + chartType: ChartTypes.XYAxis, + specType: SpecTypes.Series, + id: spec2Id, + groupId: GROUP_ID, + seriesType: SeriesTypes.Bubble, + yScaleToDataExtent: false, + 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 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, + ); + }); + + 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); + + 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); + }); + 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, + }, + 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, + }, + transform: { + x: 25, + y: 0, + }, + } as unknown) as PointGeometry); + expect(indexedGeometryMap.size).toEqual(points.length); + }); + 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, + }, + 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, + }, + transform: { + x: 25, + y: 0, + }, + } as unknown) as PointGeometry); + expect(indexedGeometryMap.size).toEqual(points.length); + }); + }); + describe('Single series bubble chart - linear', () => { + const pointSeriesSpec: BubbleSeriesSpec = { + chartType: ChartTypes.XYAxis, + specType: SpecTypes.Series, + id: SPEC_ID, + groupId: GROUP_ID, + seriesType: SeriesTypes.Bubble, + yScaleToDataExtent: false, + 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 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 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); + }); + 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, + }, + 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, + }, + transform: { + x: 0, + y: 0, + }, + } as unknown) as PointGeometry); + expect(indexedGeometryMap.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, + id: spec1Id, + groupId: GROUP_ID, + seriesType: SeriesTypes.Bubble, + yScaleToDataExtent: false, + data: [ + [0, 10], + [1, 5], + ], + xAccessor: 0, + yAccessors: [1], + xScaleType: ScaleType.Linear, + yScaleType: ScaleType.Linear, + }; + const pointSeriesSpec2: BubbleSeriesSpec = { + chartType: ChartTypes.XYAxis, + specType: SpecTypes.Series, + id: spec2Id, + groupId: GROUP_ID, + seriesType: SeriesTypes.Bubble, + yScaleToDataExtent: false, + 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 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, + { + 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); + + 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); + }); + 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, + }, + 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, + }, + transform: { + x: 0, + y: 0, + }, + } as unknown) as PointGeometry); + expect(indexedGeometryMap.size).toEqual(points.length); + }); + 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, + }, + 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, + }, + transform: { + x: 0, + y: 0, + }, + } as unknown) as PointGeometry); + expect(indexedGeometryMap.size).toEqual(points.length); + }); + }); + describe('Single series bubble chart - time', () => { + const pointSeriesSpec: BubbleSeriesSpec = { + chartType: ChartTypes.XYAxis, + specType: SpecTypes.Series, + id: SPEC_ID, + groupId: GROUP_ID, + seriesType: SeriesTypes.Bubble, + yScaleToDataExtent: false, + 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 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 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); + }); + 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, + }, + 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, + }, + transform: { + x: 0, + y: 0, + }, + } as unknown) as PointGeometry); + expect(indexedGeometryMap.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, + id: spec1Id, + groupId: GROUP_ID, + seriesType: SeriesTypes.Bubble, + yScaleToDataExtent: false, + data: [ + [1546300800000, 10], + [1546387200000, 5], + ], + xAccessor: 0, + yAccessors: [1], + xScaleType: ScaleType.Time, + yScaleType: ScaleType.Linear, + }; + const pointSeriesSpec2: BubbleSeriesSpec = { + chartType: ChartTypes.XYAxis, + specType: SpecTypes.Series, + id: spec2Id, + groupId: GROUP_ID, + seriesType: SeriesTypes.Bubble, + yScaleToDataExtent: false, + 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 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, + { + enabled: false, + }, + false, + ); + }); + 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: 1546300800000, + y: 10, + mark: null, + }, + 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, + }, + transform: { + x: 0, + y: 0, + }, + } as unknown) as PointGeometry); + expect(indexedGeometryMap.size).toEqual(points.length); + }); + 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, + }, + 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, + }, + transform: { + x: 0, + y: 0, + }, + } as unknown) as PointGeometry); + expect(indexedGeometryMap.size).toEqual(points.length); + }); + }); + describe('Single series bubble chart - y log', () => { + const pointSeriesSpec: BubbleSeriesSpec = { + chartType: ChartTypes.XYAxis, + specType: SpecTypes.Series, + id: SPEC_ID, + groupId: GROUP_ID, + seriesType: SeriesTypes.Bubble, + yScaleToDataExtent: false, + data: [ + [0, 10], + [1, 5], + [2, null], + [3, 5], + [4, 5], + [5, 0], + [6, 10], + [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 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); + }); + test('Can render points', () => { + const { + bubbleGeometry: { points }, + indexedGeometryMap, + } = renderedBubble; + // 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 zeroValueIndexdGeometry = indexedGeometryMap.find(null, { + x: 56.25, + y: 100, + }); + expect(zeroValueIndexdGeometry).toBeDefined(); + expect(zeroValueIndexdGeometry.length).toBe(5); + expect(zeroValueIndexdGeometry.find(({ value: { x } }) => x === 5)).toBeDefined(); + // moved to the bottom of the chart + expect((zeroValueIndexdGeometry[0] as PointGeometry).y).toBe(100); + // 0 radius point + expect((zeroValueIndexdGeometry[0] as PointGeometry).radius).toBe(0); + }); + }); + describe('Remove points datum is not in domain', () => { + const pointSeriesSpec: BubbleSeriesSpec = { + chartType: ChartTypes.XYAxis, + specType: SpecTypes.Series, + id: SPEC_ID, + groupId: GROUP_ID, + seriesType: SeriesTypes.Bubble, + yScaleToDataExtent: false, + 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 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, + { + enabled: false, + }, + false, + ); + }); + test('Can render two points', () => { + const { + bubbleGeometry: { points }, + indexedGeometryMap, + } = renderedBubble; + // 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', + x: 0, + y: 0, + mark: null, + }, + 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, + }, + 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, + yScaleToDataExtent: false, + 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, + ); + }); + + 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 8240174cd6..8b59bc89e6 100644 --- a/src/chart_types/xy_chart/rendering/rendering.lines.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.lines.test.ts @@ -23,10 +23,11 @@ import { renderLine } from './rendering'; import { computeXScale, computeYScales } from '../utils/scales'; import { LineSeriesSpec, DomainRange, SeriesTypes } from '../utils/specs'; import { LIGHT_THEME } from '../../../utils/themes/light_theme'; -import { LineGeometry, IndexedGeometry, PointGeometry } from '../../../utils/geometry'; +import { LineGeometry, PointGeometry } from '../../../utils/geometry'; import { GroupId } from '../../../utils/ids'; import { ChartTypes } from '../..'; import { SpecTypes } from '../../../specs/settings'; +import { IndexedGeometryMap } from '../utils/indexed_geometry_map'; const SPEC_ID = 'spec_1'; const GROUP_ID = 'group_1'; @@ -59,7 +60,7 @@ describe('Rendering points - line', () => { const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedLine: { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -73,6 +74,9 @@ describe('Rendering points - line', () => { false, 0, LIGHT_THEME.lineSeriesStyle, + { + enabled: false, + }, ); }); test('Can render the geometry without a line', () => { @@ -111,7 +115,7 @@ describe('Rendering points - line', () => { const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedLine: { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -125,6 +129,9 @@ describe('Rendering points - line', () => { false, 0, LIGHT_THEME.lineSeriesStyle, + { + enabled: false, + }, ); }); test('Can render a line', () => { @@ -138,13 +145,13 @@ describe('Rendering points - line', () => { test('Can render two points', () => { const { lineGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = renderedLine; expect(points[0]).toEqual(({ x: 0, y: 0, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -158,6 +165,7 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 0, y: 10, + mark: null, }, transform: { x: 25, @@ -167,7 +175,7 @@ describe('Rendering points - line', () => { expect(points[1]).toEqual(({ x: 50, y: 50, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -180,13 +188,14 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1, y: 5, + mark: null, }, transform: { x: 25, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); }); describe('Multi series line chart - ordinal', () => { @@ -235,11 +244,11 @@ describe('Rendering points - line', () => { let firstLine: { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; let secondLine: { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -253,6 +262,9 @@ describe('Rendering points - line', () => { false, 0, LIGHT_THEME.lineSeriesStyle, + { + enabled: false, + }, ); secondLine = renderLine( 25, // adding a ideal 25px shift, generally applied by renderGeometries @@ -264,6 +276,9 @@ describe('Rendering points - line', () => { false, 0, LIGHT_THEME.lineSeriesStyle, + { + enabled: false, + }, ); }); @@ -283,13 +298,13 @@ describe('Rendering points - line', () => { test('can render first spec points', () => { const { lineGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = firstLine; expect(points.length).toEqual(2); expect(points[0]).toEqual(({ x: 0, y: 50, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: spec1Id, @@ -302,6 +317,7 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 0, y: 10, + mark: null, }, transform: { x: 25, @@ -312,7 +328,7 @@ describe('Rendering points - line', () => { x: 50, y: 75, color: 'red', - radius: 10, + radius: 0, seriesIdentifier: { specId: spec1Id, key: 'spec{point1}yAccessor{1}splitAccessors{}', @@ -324,25 +340,26 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1, y: 5, + mark: null, }, transform: { x: 25, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); test('can render second spec points', () => { const { lineGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = secondLine; expect(points.length).toEqual(2); expect(points[0]).toEqual(({ x: 0, y: 0, color: 'blue', - radius: 10, + radius: 0, seriesIdentifier: { specId: spec2Id, key: 'spec{point2}yAccessor{1}splitAccessors{}', @@ -354,6 +371,7 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 0, y: 20, + mark: null, }, transform: { x: 25, @@ -364,7 +382,7 @@ describe('Rendering points - line', () => { x: 50, y: 50, color: 'blue', - radius: 10, + radius: 0, seriesIdentifier: { specId: spec2Id, key: 'spec{point2}yAccessor{1}splitAccessors{}', @@ -376,13 +394,14 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1, y: 10, + mark: null, }, transform: { x: 25, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); }); describe('Single series line chart - linear', () => { @@ -413,7 +432,7 @@ describe('Rendering points - line', () => { let renderedLine: { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -427,6 +446,9 @@ describe('Rendering points - line', () => { false, 0, LIGHT_THEME.lineSeriesStyle, + { + enabled: false, + }, ); }); test('Can render a linear line', () => { @@ -439,13 +461,13 @@ describe('Rendering points - line', () => { test('Can render two points', () => { const { lineGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = renderedLine; expect(points[0]).toEqual(({ x: 0, y: 0, color: 'red', - radius: 10, + radius: 0, seriesIdentifier: { specId: SPEC_ID, key: 'spec{spec_1}yAccessor{1}splitAccessors{}', @@ -457,6 +479,7 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 0, y: 10, + mark: null, }, transform: { x: 0, @@ -467,7 +490,7 @@ describe('Rendering points - line', () => { x: 100, y: 50, color: 'red', - radius: 10, + radius: 0, seriesIdentifier: { specId: SPEC_ID, key: 'spec{spec_1}yAccessor{1}splitAccessors{}', @@ -479,13 +502,14 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1, y: 5, + mark: null, }, transform: { x: 0, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); }); describe('Multi series line chart - linear', () => { @@ -534,11 +558,11 @@ describe('Rendering points - line', () => { let firstLine: { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; let secondLine: { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -552,6 +576,9 @@ describe('Rendering points - line', () => { false, 0, LIGHT_THEME.lineSeriesStyle, + { + enabled: false, + }, ); secondLine = renderLine( 0, // not applied any shift, renderGeometries applies it only with mixed charts @@ -563,6 +590,9 @@ describe('Rendering points - line', () => { false, 0, LIGHT_THEME.lineSeriesStyle, + { + enabled: false, + }, ); }); test('can render two linear lines', () => { @@ -581,13 +611,13 @@ describe('Rendering points - line', () => { test('can render first spec points', () => { const { lineGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = firstLine; expect(points.length).toEqual(2); expect(points[0]).toEqual(({ x: 0, y: 50, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: spec1Id, @@ -600,6 +630,7 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 0, y: 10, + mark: null, }, transform: { x: 0, @@ -609,7 +640,7 @@ describe('Rendering points - line', () => { expect(points[1]).toEqual(({ x: 100, y: 75, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: spec1Id, @@ -622,25 +653,26 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1, y: 5, + mark: null, }, transform: { x: 0, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); test('can render second spec points', () => { const { lineGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = secondLine; expect(points.length).toEqual(2); expect(points[0]).toEqual(({ x: 0, y: 0, color: 'blue', - radius: 10, + radius: 0, seriesIdentifier: { specId: spec2Id, key: 'spec{point2}yAccessor{1}splitAccessors{}', @@ -652,6 +684,7 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 0, y: 20, + mark: null, }, transform: { x: 0, @@ -662,7 +695,7 @@ describe('Rendering points - line', () => { x: 100, y: 50, color: 'blue', - radius: 10, + radius: 0, seriesIdentifier: { specId: spec2Id, key: 'spec{point2}yAccessor{1}splitAccessors{}', @@ -674,13 +707,14 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1, y: 10, + mark: null, }, transform: { x: 0, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); }); describe('Single series line chart - time', () => { @@ -711,7 +745,7 @@ describe('Rendering points - line', () => { let renderedLine: { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -725,6 +759,9 @@ describe('Rendering points - line', () => { false, 0, LIGHT_THEME.lineSeriesStyle, + { + enabled: false, + }, ); }); test('Can render a time line', () => { @@ -737,12 +774,12 @@ describe('Rendering points - line', () => { test('Can render two points', () => { const { lineGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = renderedLine; expect(points[0]).toEqual(({ x: 0, y: 0, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -755,6 +792,7 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1546300800000, y: 10, + mark: null, }, transform: { x: 0, @@ -764,7 +802,7 @@ describe('Rendering points - line', () => { expect(points[1]).toEqual(({ x: 100, y: 50, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -777,13 +815,14 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1546387200000, y: 5, + mark: null, }, transform: { x: 0, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); }); describe('Multi series line chart - time', () => { @@ -832,11 +871,11 @@ describe('Rendering points - line', () => { let firstLine: { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; let secondLine: { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -850,6 +889,9 @@ describe('Rendering points - line', () => { false, 0, LIGHT_THEME.lineSeriesStyle, + { + enabled: false, + }, ); secondLine = renderLine( 0, // not applied any shift, renderGeometries applies it only with mixed charts @@ -861,18 +903,21 @@ describe('Rendering points - line', () => { false, 0, LIGHT_THEME.lineSeriesStyle, + { + enabled: false, + }, ); }); test('can render first spec points', () => { const { lineGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = firstLine; expect(points.length).toEqual(2); expect(points[0]).toEqual(({ x: 0, y: 50, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: spec1Id, @@ -885,6 +930,7 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1546300800000, y: 10, + mark: null, }, transform: { x: 0, @@ -894,7 +940,7 @@ describe('Rendering points - line', () => { expect(points[1]).toEqual(({ x: 100, y: 75, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: spec1Id, @@ -907,24 +953,25 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1546387200000, y: 5, + mark: null, }, transform: { x: 0, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); test('can render second spec points', () => { const { lineGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = secondLine; expect(points.length).toEqual(2); expect(points[0]).toEqual(({ x: 0, y: 0, - radius: 10, + radius: 0, color: 'blue', seriesIdentifier: { specId: spec2Id, @@ -937,6 +984,7 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1546300800000, y: 20, + mark: null, }, transform: { x: 0, @@ -946,7 +994,7 @@ describe('Rendering points - line', () => { expect(points[1]).toEqual(({ x: 100, y: 50, - radius: 10, + radius: 0, color: 'blue', seriesIdentifier: { specId: spec2Id, @@ -959,13 +1007,14 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1546387200000, y: 10, + mark: null, }, transform: { x: 0, y: 0, }, } as unknown) as PointGeometry); - expect(indexedGeometries.size).toEqual(points.length); + expect(indexedGeometryMap.size).toEqual(points.length); }); }); describe('Single series line chart - y log', () => { @@ -1003,7 +1052,7 @@ describe('Rendering points - line', () => { let renderedLine: { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -1017,6 +1066,9 @@ describe('Rendering points - line', () => { false, 0, LIGHT_THEME.lineSeriesStyle, + { + enabled: false, + }, ); }); test('Can render a splitted line', () => { @@ -1030,16 +1082,16 @@ describe('Rendering points - line', () => { test('Can render points', () => { const { lineGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = renderedLine; // all the points minus the undefined ones on a log scale expect(points.length).toBe(7); // all the points expect null geometries - expect(indexedGeometries.size).toEqual(8); - const nullIndexdGeometry = indexedGeometries.get(2)!; - expect(nullIndexdGeometry).toBeUndefined(); + expect(indexedGeometryMap.size).toEqual(8); + const nullIndexdGeometry = indexedGeometryMap.find(2)!; + expect(nullIndexdGeometry).toEqual([]); - const zeroValueIndexdGeometry = indexedGeometries.get(5)!; + const zeroValueIndexdGeometry = indexedGeometryMap.find(5)!; expect(zeroValueIndexdGeometry).toBeDefined(); expect(zeroValueIndexdGeometry.length).toBe(1); // moved to the bottom of the chart @@ -1082,7 +1134,7 @@ describe('Rendering points - line', () => { const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedLine: { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; }; beforeEach(() => { @@ -1096,21 +1148,24 @@ describe('Rendering points - line', () => { false, 0, LIGHT_THEME.lineSeriesStyle, + { + enabled: false, + }, ); }); test('Can render two points', () => { const { lineGeometry: { points }, - indexedGeometries, + indexedGeometryMap, } = renderedLine; // 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(indexedGeometries.size).toEqual(3); + expect(indexedGeometryMap.size).toEqual(3); expect(points[0]).toEqual(({ x: 0, y: 100, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -1123,6 +1178,7 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 0, y: 0, + mark: null, }, transform: { x: 25, @@ -1132,7 +1188,7 @@ describe('Rendering points - line', () => { expect(points[1]).toEqual(({ x: 50, y: 0, - radius: 10, + radius: 0, color: 'red', seriesIdentifier: { specId: SPEC_ID, @@ -1145,6 +1201,7 @@ describe('Rendering points - line', () => { accessor: 'y1', x: 1, y: 1, + mark: null, }, transform: { x: 25, @@ -1153,4 +1210,86 @@ describe('Rendering points - line', () => { } 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, + yScaleToDataExtent: false, + 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, + }, + ); + }); + + 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 c70a500293..c13f1a20aa 100644 --- a/src/chart_types/xy_chart/rendering/rendering.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.test.ts @@ -22,6 +22,7 @@ import { getBarStyleOverrides, getPointStyleOverrides, getClippedRanges, + getRadiusFn, } from './rendering'; import { BarSeriesStyle, SharedGeometryStateStyle, PointStyle } from '../../../utils/themes/theme'; import { DataSeriesDatum, XYChartSeriesIdentifier } from '../utils/series'; @@ -32,7 +33,7 @@ import { MockScale } from '../../../mocks/scale'; import { LegendItem } from '../../../commons/legend'; describe('Rendering utils', () => { - test('check if point is in geometry', () => { + test('check if point is on geometry', () => { const seriesStyle = { rect: { opacity: 1, @@ -64,6 +65,7 @@ describe('Rendering utils', () => { accessor: 'y1', x: 0, y: 0, + mark: null, }, x: 0, y: 0, @@ -79,7 +81,7 @@ describe('Rendering utils', () => { expect(isPointOnGeometry(-11, 0, geometry)).toBe(false); expect(isPointOnGeometry(11, 11, geometry)).toBe(false); }); - test('check if point is in point geometry', () => { + test('check if point is on point geometry', () => { const geometry: PointGeometry = { color: 'red', seriesIdentifier: { @@ -93,6 +95,7 @@ describe('Rendering utils', () => { accessor: 'y1', x: 0, y: 0, + mark: null, }, transform: { x: 0, @@ -102,14 +105,24 @@ describe('Rendering utils', () => { y: 0, radius: 10, }; - expect(isPointOnGeometry(0, 0, geometry)).toBe(true); - expect(isPointOnGeometry(10, 10, geometry)).toBe(true); - expect(isPointOnGeometry(0, 10, geometry)).toBe(true); - expect(isPointOnGeometry(10, 0, geometry)).toBe(true); - expect(isPointOnGeometry(11, 11, geometry)).toBe(false); - expect(isPointOnGeometry(-10, 0, geometry)).toBe(true); - expect(isPointOnGeometry(-11, 0, geometry)).toBe(false); - expect(isPointOnGeometry(11, 11, geometry)).toBe(false); + // with buffer + expect(isPointOnGeometry(10, 10, geometry, 10)).toBe(true); + expect(isPointOnGeometry(20, 20, geometry, 5)).toBe(false); + + // without buffer + expect(isPointOnGeometry(0, 0, geometry, 0)).toBe(true); + expect(isPointOnGeometry(0, 10, geometry, 0)).toBe(true); + expect(isPointOnGeometry(10, 0, geometry, 0)).toBe(true); + expect(isPointOnGeometry(11, 11, geometry, 0)).toBe(false); + expect(isPointOnGeometry(-10, 0, geometry, 0)).toBe(true); + expect(isPointOnGeometry(-11, 0, geometry, 0)).toBe(false); + expect(isPointOnGeometry(11, 11, geometry, 0)).toBe(false); + + // should use radial check + expect(isPointOnGeometry(9, 9, geometry, 0)).toBe(false); + expect(isPointOnGeometry(-9, 9, geometry, 0)).toBe(false); + expect(isPointOnGeometry(9, -9, geometry, 0)).toBe(false); + expect(isPointOnGeometry(-9, -9, geometry, 0)).toBe(false); }); describe('should get common geometry style dependent on legend item highlight state', () => { @@ -231,6 +244,8 @@ describe('Rendering utils', () => { y0: 3, initialY1: 4, initialY0: 5, + mark: null, + datum: null, }; const seriesIdentifier: XYChartSeriesIdentifier = { specId: 'test', @@ -323,6 +338,8 @@ describe('Rendering utils', () => { y0: 3, initialY1: 4, initialY0: 5, + mark: null, + datum: null, }; const seriesIdentifier: XYChartSeriesIdentifier = { specId: 'test', @@ -437,4 +454,142 @@ describe('Rendering utils', () => { expect(xScale.scale).toHaveBeenCalledWith(dataSeries.data[12].x); }); }); + describe('#getRadiusFn', () => { + describe('empty data', () => { + const getRadius = getRadiusFn([], 1); + + it('should return a function', () => { + expect(getRadius).toBeFunction(); + }); + + it.each<[number, number]>([ + [0, 0], + [10, 10], + [1000, 1000], + [10000, 10000], + ])('should always return 0 - %#', (...args) => { + expect(getRadius(...args)).toBe(0); + }); + }); + + describe('default markSizeRatio', () => { + const { data } = MockDataSeries.random( + { + count: 20, + mark: { min: 500, max: 1000 }, + }, + true, + ); + const getRadius = getRadiusFn(data, 1); + + it('should return a function', () => { + expect(getRadius).toBeFunction(); + }); + + describe('Dataset validations', () => { + const expectedValues = [ + 15.29, + 40.89, + 13.39, + 36.81, + 44.66, + 44.34, + 51.01, + 6.97, + 34.04, + 49.07, + 45.11, + 25.44, + 8.98, + 9.33, + 50.62, + 48.89, + 44.34, + 1, + 33.09, + 5.94, + ]; + it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedValues[i]]))( + 'should return stepped value from domain - data[%#]', + (mark, expected) => { + expect(getRadius(mark)).toBeCloseTo(expected, 1); + }, + ); + }); + + it('should return default values when mark is null', () => { + expect(getRadius(null, 111)).toBe(111); + }); + }); + + describe('variable markSizeRatio', () => { + const { data } = MockDataSeries.random( + { + count: 5, + mark: { min: 0, max: 100 }, + }, + true, + ); + + describe('markSizeRatio - -100', () => { + // Should be treated as 0 + const getRadius = getRadiusFn(data, 1, -100); + it.each<[number | null]>(data.map(({ mark }) => [mark]))('should return stepped value - data[%#]', (mark) => { + expect(getRadius(mark)).toBe(1); + }); + }); + + describe('markSizeRatio - 0', () => { + const getRadius = getRadiusFn(data, 1, 0); + it.each<[number | null]>(data.map(({ mark }) => [mark]))('should return stepped value - data[%#]', (mark) => { + expect(getRadius(mark)).toBe(1); + }); + }); + + describe('markSizeRatio - 1', () => { + const getRadius = getRadiusFn(data, 1, 1); + const expectedRadii = [2.62, 2.59, 1, 2.73, 2.63]; + it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedRadii[i]]))( + 'should return stepped value - data[%#]', + (mark, expected) => { + expect(getRadius(mark)).toBeCloseTo(expected, 1); + }, + ); + }); + + describe('markSizeRatio - 10', () => { + const getRadius = getRadiusFn(data, 1, 10); + const expectedRadii = [9.09, 8.56, 1, 11.1, 9.38]; + it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedRadii[i]]))( + 'should return stepped value - data[%#]', + (mark, expected) => { + expect(getRadius(mark)).toBeCloseTo(expected, 1); + }, + ); + }); + + describe('markSizeRatio - 100', () => { + const getRadius = getRadiusFn(data, 1, 100); + const expectedRadii = [80.71, 75.37, 1, 101.0, 83.61]; + it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedRadii[i]]))( + 'should return stepped value - data[%#]', + (mark, expected) => { + expect(getRadius(mark)).toBeCloseTo(expected, 1); + }, + ); + }); + + describe('markSizeRatio - 1000', () => { + // Should be treated as 100 + const getRadius = getRadiusFn(data, 1, 1000); + const expectedRadii = [80.71, 75.37, 1, 101.0, 83.61]; + it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedRadii[i]]))( + 'should return stepped value - data[%#]', + (mark, expected) => { + expect(getRadius(mark)).toBeCloseTo(expected, 1); + }, + ); + }); + }); + }); }); diff --git a/src/chart_types/xy_chart/rendering/rendering.ts b/src/chart_types/xy_chart/rendering/rendering.ts index 665a4890f1..90c604b160 100644 --- a/src/chart_types/xy_chart/rendering/rendering.ts +++ b/src/chart_types/xy_chart/rendering/rendering.ts @@ -25,13 +25,14 @@ import { SharedGeometryStateStyle, BarSeriesStyle, GeometryStateStyle, + LineStyle, + BubbleSeriesStyle, } from '../../../utils/themes/theme'; import { Scale, ScaleType, isLogarithmicScale } from '../../../scales'; import { CurveType, getCurveFactory } from '../../../utils/curves'; import { DataSeriesDatum, DataSeries, XYChartSeriesIdentifier } from '../utils/series'; import { DisplayValueSpec, PointStyleAccessor, BarStyleAccessor } from '../utils/specs'; import { - IndexedGeometry, PointGeometry, BarGeometry, AreaGeometry, @@ -39,23 +40,19 @@ import { isPointGeometry, ClippedRanges, BandedAccessorType, + BubbleGeometry, } from '../../../utils/geometry'; import { mergePartial, Color } from '../../../utils/commons'; import { LegendItem } from '../../../commons/legend'; +import { IndexedGeometryMap, GeometryType } from '../utils/indexed_geometry_map'; +import { getDistance } from '../state/utils'; +import { MarkBuffer } from '../../../specs'; -/** @internal */ -export function mutableIndexedGeometryMapUpsert( - mutableGeometriesIndex: Map, - key: any, - geometry: IndexedGeometry | IndexedGeometry[], -) { - const existing = mutableGeometriesIndex.get(key); - const upsertGeometry: IndexedGeometry[] = Array.isArray(geometry) ? geometry : [geometry]; - if (existing === undefined) { - mutableGeometriesIndex.set(key, upsertGeometry); - } else { - mutableGeometriesIndex.set(key, [...upsertGeometry, ...existing]); - } +export const DEFAULT_HIGHLIGHT_PADDING = 10; + +export interface MarkSizeOptions { + enabled: boolean; + ratio?: number; } /** @internal */ @@ -107,27 +104,78 @@ export function getBarStyleOverrides( }); } +type GetRadiusFnReturn = (mark: number | null, defaultRadius?: number) => number; + +/** + * Get radius function form ratio and min/max mark size + * + * @todo add continuous/non-stepped function + * + * @param {Datum[]} radii + * @param {number} lineWidth + * @param {number=50} markSizeRatio - 0 to 100 + * @internal + */ +export function getRadiusFn(data: DataSeriesDatum[], lineWidth: number, markSizeRatio: number = 50): GetRadiusFnReturn { + if (data.length === 0) { + return () => 0; + } + const { min, max } = data.reduce( + (acc, { mark }) => + mark === null + ? acc + : { + min: Math.min(acc.min, mark / 2), + max: Math.max(acc.max, mark / 2), + }, + { min: Infinity, max: -Infinity }, + ); + const adjustedMarkSizeRatio = Math.min(Math.max(markSizeRatio, 0), 100); + const radiusStep = (max - min || max * 100) / Math.pow(adjustedMarkSizeRatio, 2); + return function getRadius(mark, defaultRadius = 0): number { + if (mark === null) { + return defaultRadius; + } + const circleRadius = (mark / 2 - min) / radiusStep; + const baseMagicNumber = 2; + const base = circleRadius ? Math.sqrt(circleRadius + baseMagicNumber) + lineWidth : lineWidth; + return base; + }; +} + function renderPoints( shift: number, dataSeries: DataSeries, xScale: Scale, yScale: Scale, color: Color, + lineStyle: LineStyle, hasY0Accessors: boolean, + markSizeOptions: MarkSizeOptions, styleAccessor?: PointStyleAccessor, + spatial = false, ): { pointGeometries: PointGeometry[]; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; } { - const indexedGeometries: Map = new Map(); + const indexedGeometryMap = new IndexedGeometryMap(); const isLogScale = isLogarithmicScale(yScale); + const getRadius = markSizeOptions.enabled + ? getRadiusFn(dataSeries.data, lineStyle.strokeWidth, markSizeOptions.ratio) + : () => 0; + const geometryType = spatial ? GeometryType.spatial : GeometryType.linear; const pointGeometries = dataSeries.data.reduce((acc, datum) => { - const { x: xValue, y0, y1, initialY0, initialY1, filled } = datum; + const { x: xValue, y0, y1, initialY0, initialY1, filled, mark } = datum; // don't create the point if not within the xScale domain or it that point was filled if (!xScale.isValueInDomain(xValue) || (filled && filled.y1 !== undefined)) { return acc; } const x = xScale.scale(xValue); + + if (x === null) { + return acc; + } + const points: PointGeometry[] = []; const yDatums = hasY0Accessors ? [y0, y1] : [y1]; @@ -137,7 +185,7 @@ function renderPoints( return; } let y; - let radius = 10; + let radius = getRadius(mark); // we fix 0 and negative values at y = 0 if (yDatum === null || (isLogScale && yDatum <= 0)) { y = yScale.range[0]; @@ -145,6 +193,11 @@ function renderPoints( } else { y = yScale.scale(yDatum); } + + if (y === null) { + return acc; + } + const originalY = hasY0Accessors && index === 0 ? initialY0 : initialY1; const seriesIdentifier: XYChartSeriesIdentifier = { key: dataSeries.key, @@ -162,6 +215,7 @@ function renderPoints( value: { x: xValue, y: originalY, + mark, accessor: hasY0Accessors && index === 0 ? BandedAccessorType.Y0 : BandedAccessorType.Y1, }, transform: { @@ -171,7 +225,7 @@ function renderPoints( seriesIdentifier, styleOverrides, }; - mutableIndexedGeometryMapUpsert(indexedGeometries, xValue, pointGeometry); + indexedGeometryMap.set(pointGeometry, geometryType); // use the geometry only if the yDatum in contained in the current yScale domain const isHidden = yDatum === null || (isLogScale && yDatum <= 0); if (!isHidden && yScale.isValueInDomain(yDatum)) { @@ -182,7 +236,7 @@ function renderPoints( }, [] as PointGeometry[]); return { pointGeometries, - indexedGeometries, + indexedGeometryMap, }; } @@ -199,9 +253,9 @@ export function renderBars( minBarHeight?: number, ): { barGeometries: BarGeometry[]; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; } { - const indexedGeometries: Map = new Map(); + const indexedGeometryMap = new IndexedGeometryMap(); const barGeometries: BarGeometry[] = []; const bboxCalculator = new CanvasTextBBoxCalculator(); @@ -223,7 +277,7 @@ export function renderBars( return; } - let y = 0; + let y: number | null = 0; let y0Scaled; if (yScale.type === ScaleType.Log) { y = y1 === 0 || y1 === null ? yScale.range[0] : yScale.scale(y1); @@ -242,6 +296,10 @@ export function renderBars( } } + if (y === null || y0Scaled === null) { + return; + } + let height = y0Scaled - y; // handle minBarHeight adjustment @@ -256,7 +314,13 @@ export function renderBars( } } - const x = xScale.scale(datum.x) + xScale.bandwidth * orderIndex; + const xScaled = xScale.scale(datum.x); + + if (xScaled === null) { + return; + } + + const x = xScaled + xScale.bandwidth * orderIndex; const width = xScale.bandwidth; const formattedDisplayValue = @@ -310,12 +374,13 @@ export function renderBars( value: { x: datum.x, y: initialY1, + mark: null, accessor: BandedAccessorType.Y1, }, seriesIdentifier, seriesStyle, }; - mutableIndexedGeometryMapUpsert(indexedGeometries, datum.x, barGeometry); + indexedGeometryMap.set(barGeometry); barGeometries.push(barGeometry); }); @@ -323,7 +388,7 @@ export function renderBars( return { barGeometries, - indexedGeometries, + indexedGeometryMap, }; } @@ -338,21 +403,22 @@ export function renderLine( hasY0Accessors: boolean, xScaleOffset: number, seriesStyle: LineSeriesStyle, + markSizeOptions: MarkSizeOptions, pointStyleAccessor?: PointStyleAccessor, hasFit?: boolean, ): { lineGeometry: LineGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; } { const isLogScale = isLogarithmicScale(yScale); const pathGenerator = line() - .x(({ x }) => xScale.scale(x) - xScaleOffset) + .x(({ x }) => xScale.scaleOrThrow(x) - xScaleOffset) .y((datum) => { const yValue = getYValue(datum); if (yValue !== null) { - return yScale.scale(yValue); + return yScale.scaleOrThrow(yValue); } // this should never happen thanks to the defined function @@ -366,19 +432,30 @@ export function renderLine( const y = 0; const x = shift; - const { pointGeometries, indexedGeometries } = renderPoints( + const { pointGeometries, indexedGeometryMap } = renderPoints( shift - xScaleOffset, dataSeries, xScale, yScale, color, + seriesStyle.line, hasY0Accessors, + markSizeOptions, pointStyleAccessor, ); const clippedRanges = hasFit && !hasY0Accessors ? getClippedRanges(dataSeries.data, xScale, xScaleOffset) : []; + let linePath: string; + + try { + linePath = pathGenerator(dataSeries.data) || ''; + } catch (e) { + // When values are not scalable + linePath = ''; + } + const lineGeometry = { - line: pathGenerator(dataSeries.data) || '', + line: linePath, points: pointGeometries, color, transform: { @@ -398,7 +475,54 @@ export function renderLine( }; return { lineGeometry, - indexedGeometries, + indexedGeometryMap, + }; +} + +/** @internal */ +export function renderBubble( + shift: number, + dataSeries: DataSeries, + xScale: Scale, + yScale: Scale, + color: Color, + hasY0Accessors: boolean, + seriesStyle: BubbleSeriesStyle, + markSizeOptions: MarkSizeOptions, + isMixedChart: boolean, + pointStyleAccessor?: PointStyleAccessor, +): { + bubbleGeometry: BubbleGeometry; + indexedGeometryMap: IndexedGeometryMap; +} { + const { pointGeometries, indexedGeometryMap } = renderPoints( + shift, + dataSeries, + xScale, + yScale, + color, + seriesStyle.point, + hasY0Accessors, + markSizeOptions, + pointStyleAccessor, + !isMixedChart, + ); + + const bubbleGeometry = { + points: pointGeometries, + color, + seriesIdentifier: { + key: dataSeries.key, + specId: dataSeries.specId, + yAccessor: dataSeries.yAccessor, + splitAccessors: dataSeries.splitAccessors, + seriesKeys: dataSeries.seriesKeys, + }, + seriesPointStyle: seriesStyle.point, + }; + return { + bubbleGeometry, + indexedGeometryMap, }; } @@ -429,20 +553,21 @@ export function renderArea( hasY0Accessors: boolean, xScaleOffset: number, seriesStyle: AreaSeriesStyle, + markSizeOptions: MarkSizeOptions, isStacked = false, pointStyleAccessor?: PointStyleAccessor, hasFit?: boolean, ): { areaGeometry: AreaGeometry; - indexedGeometries: Map; + indexedGeometryMap: IndexedGeometryMap; } { const isLogScale = isLogarithmicScale(yScale); const pathGenerator = area() - .x(({ x }) => xScale.scale(x) - xScaleOffset) + .x(({ x }) => xScale.scaleOrThrow(x) - xScaleOffset) .y1((datum) => { const yValue = getYValue(datum); if (yValue !== null) { - return yScale.scale(yValue); + return yScale.scaleOrThrow(yValue); } // this should never happen thanks to the defined function return yScale.isInverted ? yScale.range[1] : yScale.range[0]; @@ -451,7 +576,8 @@ export function renderArea( if (y0 === null || (isLogScale && y0 <= 0)) { return yScale.range[0]; } - return yScale.scale(y0); + + return yScale.scaleOrThrow(y0); }) .defined((datum) => { const yValue = getYValue(datum); @@ -461,30 +587,56 @@ export function renderArea( const clippedRanges = hasFit && !hasY0Accessors && !isStacked ? getClippedRanges(dataSeries.data, xScale, xScaleOffset) : []; - const y1Line = pathGenerator.lineY1()(dataSeries.data); + let y1Line: string | null; + + try { + y1Line = pathGenerator.lineY1()(dataSeries.data); + } catch (e) { + // When values are not scalable + y1Line = null; + } + const lines: string[] = []; if (y1Line) { lines.push(y1Line); } if (hasY0Accessors) { - const y0Line = pathGenerator.lineY0()(dataSeries.data); + let y0Line: string | null; + + try { + y0Line = pathGenerator.lineY0()(dataSeries.data); + } catch (e) { + // When values are not scalable + y0Line = null; + } if (y0Line) { lines.push(y0Line); } } - const { pointGeometries, indexedGeometries } = renderPoints( + const { pointGeometries, indexedGeometryMap } = renderPoints( shift - xScaleOffset, dataSeries, xScale, yScale, color, + seriesStyle.line, hasY0Accessors, + markSizeOptions, pointStyleAccessor, ); + let areaPath: string; + + try { + areaPath = pathGenerator(dataSeries.data) || ''; + } catch (e) { + // When values are not scalable + areaPath = ''; + } + const areaGeometry: AreaGeometry = { - area: pathGenerator(dataSeries.data) || '', + area: areaPath, lines, points: pointGeometries, color, @@ -507,7 +659,7 @@ export function renderArea( }; return { areaGeometry, - indexedGeometries, + indexedGeometryMap, }; } @@ -523,7 +675,11 @@ export function getClippedRanges(dataset: DataSeriesDatum[], xScale: Scale, xSca let hasNull = false; return dataset.reduce((acc, { x, y1 }) => { - const xValue = xScale.scale(x) - xScaleOffset + xScale.bandwidth / 2; + const xScaled = xScale.scale(x); + if (xScaled === null) { + return acc; + } + const xValue = xScaled - xScaleOffset + xScale.bandwidth / 2; if (y1 !== null) { if (hasNull) { @@ -578,16 +734,28 @@ export function isPointOnGeometry( xCoordinate: number, yCoordinate: number, indexedGeometry: BarGeometry | PointGeometry, + buffer: MarkBuffer = DEFAULT_HIGHLIGHT_PADDING, ) { const { x, y } = indexedGeometry; if (isPointGeometry(indexedGeometry)) { const { radius, transform } = indexedGeometry; - return ( - yCoordinate >= y - radius && - yCoordinate <= y + radius && - xCoordinate >= x + transform.x - radius && - xCoordinate <= x + transform.x + radius + const distance = getDistance( + { + x: xCoordinate, + y: yCoordinate, + }, + { + x: x + transform.x, + y, + }, ); + const radiusBuffer = typeof buffer === 'number' ? buffer : buffer(radius); + + if (radiusBuffer === Infinity) { + return distance <= radius + DEFAULT_HIGHLIGHT_PADDING; + } + + return distance <= radius + radiusBuffer; } const { width, height } = indexedGeometry; return yCoordinate >= y && yCoordinate <= y + height && xCoordinate >= x && xCoordinate <= x + width; diff --git a/src/chart_types/xy_chart/specs/bubble_series.tsx b/src/chart_types/xy_chart/specs/bubble_series.tsx new file mode 100644 index 0000000000..4059e25356 --- /dev/null +++ b/src/chart_types/xy_chart/specs/bubble_series.tsx @@ -0,0 +1,63 @@ +/* + * 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 { BubbleSeriesSpec, DEFAULT_GLOBAL_ID, SeriesTypes } from '../utils/specs'; +import { ScaleType } from '../../../scales'; +import { ChartTypes } from '../../../chart_types'; +import { specComponentFactory, getConnect } from '../../../state/spec_factory'; +import { SpecTypes } from '../../../specs/settings'; + +const defaultProps = { + chartType: ChartTypes.XYAxis, + specType: SpecTypes.Series, + seriesType: SeriesTypes.Bubble, + groupId: DEFAULT_GLOBAL_ID, + xScaleType: ScaleType.Ordinal, + yScaleType: ScaleType.Linear, + xAccessor: 'x', + yAccessors: ['y'], + hideInLegend: false, + yScaleToDataExtent: false, +}; +type SpecRequiredProps = Pick; +type SpecOptionalProps = Partial>; + +/** + * @alpha + * + * This series type uses a spatial index that is incompatible with other series types. This will + * be fixed once an update has been made to the tooltip design. + * + * When used alone with other `BubbleSeries` the spatial index will be used. However when + * mixed with other series types, the linear index will be used. This will affect highlighting + * of points when using the `markSizeAccessor`. + */ +export const BubbleSeries: React.FunctionComponent = getConnect()( + specComponentFactory< + BubbleSeriesSpec, + | 'seriesType' + | 'groupId' + | 'xScaleType' + | 'yScaleType' + | 'xAccessor' + | 'yAccessors' + | 'hideInLegend' + | 'yScaleToDataExtent' + >(defaultProps), +); diff --git a/src/chart_types/xy_chart/specs/index.ts b/src/chart_types/xy_chart/specs/index.ts index 4651ca3380..331a83d8d9 100644 --- a/src/chart_types/xy_chart/specs/index.ts +++ b/src/chart_types/xy_chart/specs/index.ts @@ -16,10 +16,11 @@ * specific language governing permissions and limitations * under the License. */ +export { AreaSeries } from './area_series'; export { Axis } from './axis'; -export { LineAnnotation } from './line_annotation'; -export { RectAnnotation } from './rect_annotation'; -export { LineSeries } from './line_series'; export { BarSeries } from './bar_series'; +export { BubbleSeries } from './bubble_series'; export { HistogramBarSeries } from './histogram_bar_series'; -export { AreaSeries } from './area_series'; +export { LineAnnotation } from './line_annotation'; +export { LineSeries } from './line_series'; +export { RectAnnotation } from './rect_annotation'; diff --git a/src/chart_types/xy_chart/state/__snapshots__/utils.test.ts.snap b/src/chart_types/xy_chart/state/__snapshots__/utils.test.ts.snap index e05c85405e..f0d329a774 100644 --- a/src/chart_types/xy_chart/state/__snapshots__/utils.test.ts.snap +++ b/src/chart_types/xy_chart/state/__snapshots__/utils.test.ts.snap @@ -18,6 +18,7 @@ Array [ }, "initialY0": null, "initialY1": 1, + "mark": null, "x": 0, "y0": 0, "y1": 1, @@ -29,6 +30,7 @@ Array [ }, "initialY0": null, "initialY1": 2, + "mark": null, "x": 1, "y0": 0, "y1": 2, @@ -40,6 +42,7 @@ Array [ }, "initialY0": null, "initialY1": 10, + "mark": null, "x": 2, "y0": 0, "y1": 10, @@ -51,6 +54,7 @@ Array [ }, "initialY0": null, "initialY1": 6, + "mark": null, "x": 3, "y0": 0, "y1": 6, @@ -83,6 +87,7 @@ Array [ }, "initialY0": null, "initialY1": 1, + "mark": null, "x": 0, "y0": 0, "y1": 1, @@ -94,6 +99,7 @@ Array [ }, "initialY0": null, "initialY1": 2, + "mark": null, "x": 1, "y0": 0, "y1": 2, @@ -105,6 +111,7 @@ Array [ }, "initialY0": null, "initialY1": 10, + "mark": null, "x": 2, "y0": 0, "y1": 10, @@ -116,6 +123,7 @@ Array [ }, "initialY0": null, "initialY1": 6, + "mark": null, "x": 3, "y0": 0, "y1": 6, @@ -154,6 +162,7 @@ Array [ }, "initialY0": null, "initialY1": 1, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -166,6 +175,7 @@ Array [ }, "initialY0": null, "initialY1": 2, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -178,6 +188,7 @@ Array [ }, "initialY0": null, "initialY1": 3, + "mark": null, "x": 2, "y0": null, "y1": 3, @@ -190,6 +201,7 @@ Array [ }, "initialY0": null, "initialY1": 4, + "mark": null, "x": 3, "y0": null, "y1": 4, @@ -216,6 +228,7 @@ Array [ }, "initialY0": null, "initialY1": 2, + "mark": null, "x": 0, "y0": 1, "y1": 3, @@ -228,6 +241,7 @@ Array [ }, "initialY0": null, "initialY1": 3, + "mark": null, "x": 1, "y0": 2, "y1": 5, @@ -240,6 +254,7 @@ Array [ }, "initialY0": null, "initialY1": 4, + "mark": null, "x": 2, "y0": 3, "y1": 7, @@ -252,6 +267,7 @@ Array [ }, "initialY0": null, "initialY1": 5, + "mark": null, "x": 3, "y0": 4, "y1": 9, @@ -293,6 +309,7 @@ Array [ }, "initialY0": null, "initialY1": 1, + "mark": null, "x": 0, "y0": 0, "y1": 1, @@ -305,6 +322,7 @@ Array [ }, "initialY0": null, "initialY1": 2, + "mark": null, "x": 1, "y0": 0, "y1": 2, @@ -317,6 +335,7 @@ Array [ }, "initialY0": null, "initialY1": 3, + "mark": null, "x": 2, "y0": 0, "y1": 3, @@ -329,6 +348,7 @@ Array [ }, "initialY0": null, "initialY1": 4, + "mark": null, "x": 3, "y0": 0, "y1": 4, @@ -355,6 +375,7 @@ Array [ }, "initialY0": null, "initialY1": 2, + "mark": null, "x": 0, "y0": 0, "y1": 2, @@ -367,6 +388,7 @@ Array [ }, "initialY0": null, "initialY1": 3, + "mark": null, "x": 1, "y0": 0, "y1": 3, @@ -379,6 +401,7 @@ Array [ }, "initialY0": null, "initialY1": 4, + "mark": null, "x": 2, "y0": 0, "y1": 4, @@ -391,6 +414,7 @@ Array [ }, "initialY0": null, "initialY1": 5, + "mark": null, "x": 3, "y0": 0, "y1": 5, 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 bb4762964f..6f141d9052 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 @@ -424,6 +424,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { x: 0, y: 10, accessor: 'y1', + mark: null, }, { key: 'spec{spec_1}yAccessor{1}splitAccessors{}', @@ -468,6 +469,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { x: 0, y: 10, accessor: 'y1', + mark: null, }, { key: 'spec{spec_1}yAccessor{1}splitAccessors{}', @@ -515,6 +517,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { x: 0, y: 10, accessor: 'y1', + mark: null, }, { key: 'spec{spec_1}yAccessor{1}splitAccessors{}', @@ -567,6 +570,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { x: (spec.data[0] as Array)[0], y: (spec.data[0] as Array)[1], accessor: 'y1', + mark: null, }, { key: 'spec{spec_1}yAccessor{1}splitAccessors{}', @@ -598,6 +602,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { x: (spec.data[1] as Array)[0], y: (spec.data[1] as Array)[1], accessor: 'y1', + mark: null, }, { key: 'spec{spec_1}yAccessor{1}splitAccessors{}', @@ -693,6 +698,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { x: 1, y: 5, accessor: 'y1', + mark: null, }, { key: 'spec{spec_1}yAccessor{1}splitAccessors{}', 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 15e4fbbc41..4ffc50a70e 100644 --- a/src/chart_types/xy_chart/state/chart_state.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.test.ts @@ -944,6 +944,7 @@ describe.skip('Chart Store', () => { x: 0, y: 1, accessor: 'y1', + mark: null, }, x: 0, y: 0, @@ -964,6 +965,7 @@ describe.skip('Chart Store', () => { x: 0, y: 3, accessor: 'y1', + mark: null, }, x: 50, y: 0, @@ -1158,6 +1160,7 @@ describe.skip('Chart Store', () => { x: 0, y: 1, accessor: 'y1', + mark: null, }, x: 0, y: 0, 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 dde7bf38c7..4e093d0f8f 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 @@ -85,7 +85,7 @@ function getCursorBand( seriesSpecs: BasicSeriesSpec[], totalBarsInCluster: number, isTooltipSnapEnabled: boolean, - geometriesIndexKeys: any[], + geometriesIndexKeys: (string | number)[], ): (Dimensions & { visible: boolean }) | undefined { // update che cursorBandPosition based on chart configuration const isLineAreaOnly = isLineAreaOnlyChart(seriesSpecs); 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 1dbfb05d16..68fbb899c8 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 @@ -30,6 +30,7 @@ import { Dimensions } from '../../../../utils/dimensions'; import { GlobalChartState } from '../../../../state/chart_state'; import { isValidPointerOverEvent } from '../../../../utils/events'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { IndexedGeometryMap } from '../../utils/indexed_geometry_map'; const getExternalPointerEventStateSelector = (state: GlobalChartState) => state.externalEvents.pointer; @@ -49,8 +50,8 @@ export const getElementAtCursorPositionSelector = createCachedSelector( function getElementAtCursorPosition( orientedProjectedPoinerPosition: Point, scales: ComputedScales, - geometriesIndexKeys: any, - geometriesIndex: Map, + geometriesIndexKeys: (string | number)[], + geometriesIndex: IndexedGeometryMap, externalPointerEvent: PointerEvent | null, { chartDimensions, @@ -64,12 +65,13 @@ function getElementAtCursorPosition( if (x == null || x > chartDimensions.width + chartDimensions.left) { return []; } - return geometriesIndex.get(externalPointerEvent.value) || []; + // TODO: Handle external event with spatial points + return geometriesIndex.find(externalPointerEvent.value, { x: -1, y: -1 }); } const xValue = scales.xScale.invertWithStep(orientedProjectedPoinerPosition.x, geometriesIndexKeys); if (!xValue) { return []; } - // get the elements on at this cursor position - return geometriesIndex.get(xValue.value) || []; + // get the elements at cursor position + return geometriesIndex.find(xValue?.value, orientedProjectedPoinerPosition); } diff --git a/src/chart_types/xy_chart/state/selectors/get_geometries_index.ts b/src/chart_types/xy_chart/state/selectors/get_geometries_index.ts index 2d710afce1..2306a9c63a 100644 --- a/src/chart_types/xy_chart/state/selectors/get_geometries_index.ts +++ b/src/chart_types/xy_chart/state/selectors/get_geometries_index.ts @@ -17,14 +17,14 @@ * under the License. */ import createCachedSelector from 're-reselect'; -import { IndexedGeometry } from '../../../../utils/geometry'; import { computeSeriesGeometriesSelector } from './compute_series_geometries'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { IndexedGeometryMap } from '../../utils/indexed_geometry_map'; /** @internal */ export const getGeometriesIndexSelector = createCachedSelector( [computeSeriesGeometriesSelector], - (geometries): Map => { + (geometries): IndexedGeometryMap => { return geometries.geometriesIndex; }, )(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/get_geometries_index_keys.ts b/src/chart_types/xy_chart/state/selectors/get_geometries_index_keys.ts index 9c8f821ffe..6d225f5f69 100644 --- a/src/chart_types/xy_chart/state/selectors/get_geometries_index_keys.ts +++ b/src/chart_types/xy_chart/state/selectors/get_geometries_index_keys.ts @@ -24,7 +24,7 @@ import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; /** @internal */ export const getGeometriesIndexKeysSelector = createCachedSelector( [computeSeriesGeometriesSelector], - (seriesGeometries): any[] => { - return [...seriesGeometries.geometriesIndex.keys()].sort(compareByValueAsc); + (seriesGeometries): (number | string)[] => { + return seriesGeometries.geometriesIndex.keys().sort(compareByValueAsc); }, )(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 25e46aec47..83b7b06814 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 @@ -39,12 +39,14 @@ import { TooltipType, TooltipValueFormatter, isFollowTooltipType, + SettingsSpec, } from '../../../../specs'; import { isValidPointerOverEvent } from '../../../../utils/events'; import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { hasSingleSeriesSelector } from './has_single_series'; import { TooltipInfo } from '../../../../components/tooltip/types'; +import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; const EMPTY_VALUES = Object.freeze({ tooltip: { @@ -67,6 +69,7 @@ export const getTooltipInfoAndGeometriesSelector = createCachedSelector( [ getSeriesSpecsSelector, getAxisSpecsSelector, + getSettingsSpecSelector, getProjectedPointerPositionSelector, getOrientedProjectedPointerPositionSelector, getChartRotationSelector, @@ -77,20 +80,21 @@ export const getTooltipInfoAndGeometriesSelector = createCachedSelector( getExternalPointerEventStateSelector, getTooltipHeaderFormatterSelector, ], - getTooltipAndHighlightFromXValue, + getTooltipAndHighlightFromValue, )((state: GlobalChartState) => { return state.chartId; }); -function getTooltipAndHighlightFromXValue( +function getTooltipAndHighlightFromValue( seriesSpecs: BasicSeriesSpec[], axesSpecs: AxisSpec[], + settings: SettingsSpec, projectedPointerPosition: Point, orientedProjectedPointerPosition: Point, chartRotation: Rotation, hasSingleSeries: boolean, scales: ComputedScales, - xMatchingGeoms: IndexedGeometry[], + matchingGeoms: IndexedGeometry[], tooltipType: TooltipType = TooltipType.VerticalCursor, externalPointerEvent: PointerEvent | null, tooltipHeaderFormatter?: TooltipValueFormatter, @@ -104,20 +108,28 @@ function getTooltipAndHighlightFromXValue( let x = orientedProjectedPointerPosition.x; let y = orientedProjectedPointerPosition.y; if (isValidPointerOverEvent(scales.xScale, externalPointerEvent)) { - x = scales.xScale.pureScale(externalPointerEvent.value); + const scaledX = scales.xScale.pureScale(externalPointerEvent.value); + + if (scaledX === null) { + return EMPTY_VALUES; + } + + x = scaledX; y = 0; } else if (projectedPointerPosition.x === -1 || projectedPointerPosition.y === -1) { return EMPTY_VALUES; } - if (xMatchingGeoms.length === 0) { + if (matchingGeoms.length === 0) { return EMPTY_VALUES; } // build the tooltip value list let header: TooltipValue | null = null; const highlightedGeometries: IndexedGeometry[] = []; - const values = xMatchingGeoms + const xValues = new Set(); + + const values = matchingGeoms .filter(({ value: { y } }) => y !== null) .reduce((acc, indexedGeometry) => { const { @@ -141,14 +153,14 @@ function getTooltipAndHighlightFromXValue( let isHighlighted = false; if ( (!externalPointerEvent || isPointerOutEvent(externalPointerEvent)) && - isPointOnGeometry(x, y, indexedGeometry) + isPointOnGeometry(x, y, indexedGeometry, settings.pointBuffer) ) { isHighlighted = true; highlightedGeometries.push(indexedGeometry); } // if it's a follow tooltip, and no element is highlighted - // not add that element into the tooltip list + // do _not_ add element into tooltip list if (!isHighlighted && isFollowTooltipType(tooltipType)) { return acc; } @@ -172,9 +184,16 @@ function getTooltipAndHighlightFromXValue( header = formatTooltip(indexedGeometry, spec, true, false, hasSingleSeries, formatterAxis); } + xValues.add(indexedGeometry.value.x); + return [...acc, formattedTooltip]; }, []); + if (values.length > 1 && xValues.size === values.length) { + // TODO: remove after tooltip redesign + header = null; + } + return { tooltip: { header, diff --git a/src/chart_types/xy_chart/state/utils.test.ts b/src/chart_types/xy_chart/state/utils.test.ts index ab753e0929..d99e1a7d07 100644 --- a/src/chart_types/xy_chart/state/utils.test.ts +++ b/src/chart_types/xy_chart/state/utils.test.ts @@ -40,9 +40,11 @@ import { isHorizontalRotation, isLineAreaOnlyChart, isVerticalRotation, - mergeGeometriesIndexes, setBarSeriesAccessors, getCustomSeriesColors, + isUniqueArray, + isDefined, + isDefinedFrom, } from './utils'; import { IndexedGeometry, BandedAccessorType } from '../../../utils/geometry'; import { mergeYCustomDomainsByGroupId } from './selectors/merge_y_custom_domains'; @@ -330,6 +332,8 @@ describe('Chart State utils', () => { areasPoints: 0, lines: 0, linePoints: 0, + bubbles: 0, + bubblePoints: 0, }; expect(isChartAnimatable(geometriesCounts, false)).toBe(false); expect(isChartAnimatable(geometriesCounts, true)).toBe(true); @@ -576,10 +580,10 @@ describe('Chart State utils', () => { false, ); expect(geometries.geometriesIndex.size).toBe(4); - expect(geometries.geometriesIndex.get(0)?.length).toBe(2); - expect(geometries.geometriesIndex.get(1)?.length).toBe(2); - expect(geometries.geometriesIndex.get(2)?.length).toBe(2); - expect(geometries.geometriesIndex.get(3)?.length).toBe(2); + 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 = { @@ -640,10 +644,10 @@ describe('Chart State utils', () => { false, ); expect(geometries.geometriesIndex.size).toBe(4); - expect(geometries.geometriesIndex.get(0)?.length).toBe(2); - expect(geometries.geometriesIndex.get(1)?.length).toBe(2); - expect(geometries.geometriesIndex.get(2)?.length).toBe(2); - expect(geometries.geometriesIndex.get(3)?.length).toBe(2); + 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 = { @@ -1234,7 +1238,7 @@ describe('Chart State utils', () => { expect(geometries.geometries.bars[0].x).toBe(0); }); }); - test('can merge geometry indexes', () => { + xtest('can merge geometry indexes', () => { const map1 = new Map(); map1.set('a', [ { @@ -1242,7 +1246,7 @@ describe('Chart State utils', () => { x: 0, y: 0, color: '#1EA593', - value: { x: 0, y: 5, accessor: BandedAccessorType.Y1 }, + value: { x: 0, y: 5, accessor: BandedAccessorType.Y1, mark: null }, transform: { x: 0, y: 0 }, seriesIdentifier: { specId: 'line1', @@ -1260,7 +1264,7 @@ describe('Chart State utils', () => { x: 0, y: 175.8, color: '#2B70F7', - value: { x: 0, y: 2, accessor: BandedAccessorType.Y1 }, + value: { x: 0, y: 2, accessor: BandedAccessorType.Y1, mark: null }, transform: { x: 0, y: 0 }, seriesIdentifier: { specId: 'line2', @@ -1271,9 +1275,9 @@ describe('Chart State utils', () => { }, }, ]); - const merged = mergeGeometriesIndexes(map1, map2); - expect(merged.get('a')).toBeDefined(); - expect(merged.get('a')?.length).toBe(2); + // 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]; @@ -1496,4 +1500,121 @@ describe('Chart State utils', () => { ]; expect(isAllSeriesDeselected(legendItems2)).toBe(false); }); + + describe('#isUniqueArray', () => { + it('should return true for simple unique values', () => { + expect(isUniqueArray([1, 2])).toBe(true); + }); + + it('should return true for complex unique values', () => { + expect(isUniqueArray([{ n: 1 }, { n: 2 }], ({ n }) => n)).toBe(true); + }); + + it('should return false for simple duplicated values', () => { + expect(isUniqueArray([1, 1, 2])).toBe(false); + }); + + it('should return false for complex duplicated values', () => { + expect(isUniqueArray([{ n: 1 }, { n: 1 }, { n: 2 }], ({ n }) => n)).toBe(false); + }); + }); + + describe('#isDefined', () => { + it('should return false for null', () => { + expect(isDefined(null)).toBe(false); + }); + + it('should return false for undefined', () => { + expect(isDefined(undefined)).toBe(false); + }); + + it('should return true for zero', () => { + expect(isDefined(0)).toBe(true); + }); + + it('should return true for empty string', () => { + expect(isDefined('')).toBe(true); + }); + + it('should return true for empty false', () => { + expect(isDefined(false)).toBe(true); + }); + + it('should filter out null and undefined values', () => { + const values: (number | null | undefined)[] = [1, 2, null, 4, 5, undefined]; + const result: number[] = values.filter(isDefined); + expect(result).toEqual([1, 2, 4, 5]); + }); + }); + + describe('#isDefinedFrom', () => { + interface Test { + a?: number | string | boolean | null; + } + it('should filter out undefined values from complex types', () => { + const values: Partial[] = [ + { + a: 1, + }, + { + a: 'string', + }, + { + a: false, + }, + {}, + ]; + const result: NonNullable[] = values.filter(isDefinedFrom(({ a }) => a !== undefined)); + expect(result).toEqual(values.slice(0, -1)); + }); + + it('should filter out null values from complex types', () => { + const values: Test[] = [ + { + a: 1, + }, + { + a: 'string', + }, + { + a: false, + }, + { + a: null, + }, + ]; + const result: NonNullable[] = values.filter(isDefinedFrom(({ a }) => a !== null)); + expect(result).toEqual(values.slice(0, -1)); + }); + + it('should filter out null values from complex nested types', () => { + type NestedTest = { + aa: Test; + }; + const values: NestedTest[] = [ + { + aa: { a: 1 }, + }, + { + aa: { a: 'string' }, + }, + { + aa: { a: false }, + }, + { + aa: { a: null }, + }, + { + aa: {}, + }, + { + aa: { a: undefined }, + }, + ]; + const result: NonNullable[] = values.filter( + isDefinedFrom(({ aa }) => aa?.a !== undefined && aa?.a !== null), + ); + expect(result).toEqual(values.slice(0, 3)); + }); + }); }); diff --git a/src/chart_types/xy_chart/state/utils.ts b/src/chart_types/xy_chart/state/utils.ts index cb5594c81d..d8ddb76c13 100644 --- a/src/chart_types/xy_chart/state/utils.ts +++ b/src/chart_types/xy_chart/state/utils.ts @@ -20,7 +20,7 @@ import { isVerticalAxis } from '../utils/axis_utils'; import { CurveType } from '../../../utils/curves'; import { mergeXDomain, XDomain } from '../domains/x_domain'; import { mergeYDomain, YDomain } from '../domains/y_domain'; -import { mutableIndexedGeometryMapUpsert, renderArea, renderBars, renderLine } from '../rendering/rendering'; +import { renderArea, renderBars, renderLine, renderBubble } from '../rendering/rendering'; import { computeXScale, computeYScales, countBarsInCluster } from '../utils/scales'; import { DataSeries, @@ -49,16 +49,20 @@ import { Fit, FitConfig, SeriesTypes, + isBubbleSeriesSpec, } from '../utils/specs'; import { ColorConfig, Theme } from '../../../utils/themes/theme'; -import { identity, mergePartial, Rotation, Color } from '../../../utils/commons'; +import { identity, mergePartial, Rotation, Color, RecursivePartial } from '../../../utils/commons'; import { Dimensions } from '../../../utils/dimensions'; import { Domain } from '../../../utils/domain'; import { GroupId, SpecId } from '../../../utils/ids'; import { Scale } from '../../../scales'; -import { PointGeometry, BarGeometry, AreaGeometry, LineGeometry, IndexedGeometry } from '../../../utils/geometry'; +import { PointGeometry, BarGeometry, AreaGeometry, LineGeometry, BubbleGeometry } from '../../../utils/geometry'; import { LegendItem } from '../../../commons/legend'; import { Spec } from '../../../specs'; +import { IndexedGeometryMap } from '../utils/indexed_geometry_map'; +import { Point } from '../../../utils/point'; +import { PrimitiveValue } from '../../partition_chart/layout/utils/group_by_rollup'; const MAX_ANIMATABLE_BARS = 300; const MAX_ANIMATABLE_LINES_AREA_POINTS = 600; @@ -86,6 +90,8 @@ export interface GeometriesCounts { areasPoints: number; lines: number; linePoints: number; + bubbles: number; + bubblePoints: number; } /** @internal */ @@ -99,13 +105,14 @@ export interface Geometries { bars: BarGeometry[]; areas: AreaGeometry[]; lines: LineGeometry[]; + bubbles: BubbleGeometry[]; } /** @internal */ export interface ComputedGeometries { scales: ComputedScales; geometries: Geometries; - geometriesIndex: Map; + geometriesIndex: IndexedGeometryMap; geometriesCounts: GeometriesCounts; } @@ -273,6 +280,7 @@ export function computeSeriesDomains( }; updatedSeriesCollection.set(key, updatedColorSet); }); + return { xDomain, yDomain, @@ -325,16 +333,18 @@ export function computeSeriesGeometries( const areas: AreaGeometry[] = []; const bars: BarGeometry[] = []; const lines: LineGeometry[] = []; - let stackedGeometriesIndex: Map = new Map(); - let nonStackedGeometriesIndex: Map = new Map(); + const bubbles: BubbleGeometry[] = []; + const geometriesIndex = new IndexedGeometryMap(); let orderIndex = 0; - const geometriesCounts = { + 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 } = dataSeriesGroup; @@ -361,8 +371,9 @@ export function computeSeriesGeometries( areas.push(...geometries.areas); lines.push(...geometries.lines); bars.push(...geometries.bars); + bubbles.push(...geometries.bubbles); points.push(...geometries.points); - stackedGeometriesIndex = mergeGeometriesIndexes(stackedGeometriesIndex, geometries.geometriesIndex); + geometriesIndex.merge(geometries.indexedGeometryMap); // update counts geometriesCounts.points += geometries.geometriesCounts.points; geometriesCounts.bars += geometries.geometriesCounts.bars; @@ -370,6 +381,8 @@ export function computeSeriesGeometries( 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; }); formattedDataSeries.nonStacked.map((dataSeriesGroup) => { const { groupId, dataSeries } = dataSeriesGroup; @@ -395,9 +408,10 @@ export function computeSeriesGeometries( areas.push(...geometries.areas); lines.push(...geometries.lines); bars.push(...geometries.bars); + bubbles.push(...geometries.bubbles); points.push(...geometries.points); - nonStackedGeometriesIndex = mergeGeometriesIndexes(nonStackedGeometriesIndex, geometries.geometriesIndex); + geometriesIndex.merge(geometries.indexedGeometryMap); // update counts geometriesCounts.points += geometries.geometriesCounts.points; geometriesCounts.bars += geometries.geometriesCounts.bars; @@ -405,8 +419,9 @@ export function computeSeriesGeometries( 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 geometriesIndex = mergeGeometriesIndexes(stackedGeometriesIndex, nonStackedGeometriesIndex); return { scales: { xScale, @@ -417,6 +432,7 @@ export function computeSeriesGeometries( areas, bars, lines, + bubbles, }, geometriesIndex, geometriesCounts, @@ -495,7 +511,8 @@ function renderGeometries( bars: BarGeometry[]; areas: AreaGeometry[]; lines: LineGeometry[]; - geometriesIndex: Map; + bubbles: BubbleGeometry[]; + indexedGeometryMap: IndexedGeometryMap; geometriesCounts: GeometriesCounts; } { const len = dataSeries.length; @@ -504,17 +521,18 @@ function renderGeometries( const bars: BarGeometry[] = []; const areas: AreaGeometry[] = []; const lines: LineGeometry[] = []; - const pointGeometriesIndex: Map = new Map(); - let barGeometriesIndex: Map = new Map(); - let lineGeometriesIndex: Map = new Map(); - let areaGeometriesIndex: Map = new Map(); - const geometriesCounts = { + const bubbles: BubbleGeometry[] = []; + const indexedGeometryMap = new IndexedGeometryMap(); + const isMixedChart = isUniqueArray(seriesSpecs, ({ seriesType }) => seriesType) && seriesSpecs.length > 1; + const geometriesCounts: GeometriesCounts = { points: 0, bars: 0, areas: 0, areasPoints: 0, lines: 0, linePoints: 0, + bubbles: 0, + bubblePoints: 0, }; let barIndexOffset = 0; for (i = 0; i < len; i++) { @@ -553,10 +571,34 @@ function renderGeometries( spec.styleAccessor, spec.minBarHeight, ); - barGeometriesIndex = mergeGeometriesIndexes(barGeometriesIndex, renderedBars.indexedGeometries); + indexedGeometryMap.merge(renderedBars.indexedGeometryMap); bars.push(...renderedBars.barGeometries); geometriesCounts.bars += renderedBars.barGeometries.length; barIndexOffset += 1; + } else if (isBubbleSeriesSpec(spec)) { + const bubbleShift = clusteredCount > 0 ? clusteredCount : 1; + const bubbleSeriesStyle = spec.bubbleSeriesStyle + ? mergePartial(chartTheme.bubbleSeriesStyle, spec.bubbleSeriesStyle, { mergeOptionalPartialValues: true }) + : chartTheme.bubbleSeriesStyle; + const renderedBubbles = renderBubble( + (xScale.bandwidth * bubbleShift) / 2, + ds, + xScale, + yScale, + color, + isBandedSpec(spec.y0Accessors), + bubbleSeriesStyle, + { + enabled: spec.markSizeAccessor !== undefined, + ratio: chartTheme.markSizeRatio, + }, + isMixedChart, + spec.pointStyleAccessor, + ); + indexedGeometryMap.merge(renderedBubbles.indexedGeometryMap); + bubbles.push(renderedBubbles.bubbleGeometry); + geometriesCounts.bubblePoints += renderedBubbles.bubbleGeometry.points.length; + geometriesCounts.bubbles += 1; } else if (isLineSeriesSpec(spec)) { const lineShift = clusteredCount > 0 ? clusteredCount : 1; const lineSeriesStyle = spec.lineSeriesStyle @@ -576,10 +618,14 @@ function renderGeometries( isBandedSpec(spec.y0Accessors), xScaleOffset, lineSeriesStyle, + { + enabled: (spec as LineSeriesSpec).markSizeAccessor !== undefined, + ratio: chartTheme.markSizeRatio, + }, spec.pointStyleAccessor, Boolean(spec.fit && ((spec.fit as FitConfig).type || spec.fit) !== Fit.None), ); - lineGeometriesIndex = mergeGeometriesIndexes(lineGeometriesIndex, renderedLines.indexedGeometries); + indexedGeometryMap.merge(renderedLines.indexedGeometryMap); lines.push(renderedLines.lineGeometry); geometriesCounts.linePoints += renderedLines.lineGeometry.points.length; geometriesCounts.lines += 1; @@ -592,7 +638,6 @@ function renderGeometries( const renderedAreas = renderArea( // move the point on half of the bandwidth if we have mixed bars/lines (xScale.bandwidth * areaShift) / 2, - ds, xScale, yScale, @@ -601,28 +646,28 @@ function renderGeometries( isBandedSpec(spec.y0Accessors), xScaleOffset, areaSeriesStyle, + { + enabled: (spec as LineSeriesSpec).markSizeAccessor !== undefined, + ratio: chartTheme.markSizeRatio, + }, isStacked, spec.pointStyleAccessor, Boolean(spec.fit && ((spec.fit as FitConfig).type || spec.fit) !== Fit.None), ); - areaGeometriesIndex = mergeGeometriesIndexes(areaGeometriesIndex, renderedAreas.indexedGeometries); + indexedGeometryMap.merge(renderedAreas.indexedGeometryMap); areas.push(renderedAreas.areaGeometry); geometriesCounts.areasPoints += renderedAreas.areaGeometry.points.length; geometriesCounts.areas += 1; } } - const geometriesIndex = mergeGeometriesIndexes( - pointGeometriesIndex, - lineGeometriesIndex, - areaGeometriesIndex, - barGeometriesIndex, - ); + return { points, bars, areas, lines, - geometriesIndex, + bubbles, + indexedGeometryMap, geometriesCounts, }; } @@ -681,24 +726,6 @@ export function computeChartTransform(chartDimensions: Dimensions, chartRotation } } -/** - * Merge multiple geometry indexes maps together. - * @param iterables a set of maps to be merged - * @returns a new Map where each element with the same key are concatenated on a single - * IndexedGemoetry array for that key - * @internal - */ -export function mergeGeometriesIndexes(...iterables: Map[]) { - const geometriesIndex: Map = new Map(); - - for (const iterable of iterables) { - for (const item of iterable) { - mutableIndexedGeometryMapUpsert(geometriesIndex, item[0], item[1]); - } - } - return geometriesIndex; -} - /** @internal */ export function isHorizontalRotation(chartRotation: Rotation) { return chartRotation === 0 || chartRotation === 180; @@ -740,3 +767,84 @@ export function isAllSeriesDeselected(legendItems: LegendItem[]): boolean { } return true; } + +/** @internal */ +export function getDistance(a: Point, b: Point): number { + return Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2)); +} + +/** @internal */ +export function stringifyNullsUndefined(value?: PrimitiveValue): string | number { + if (value === undefined) { + return 'undefined'; + } + + if (value === null) { + return 'null'; + } + + return value; +} + +/** + * Determines if an array has all unique values + * + * examples: + * ```ts + * isUniqueArray([1, 2]) // => true + * isUniqueArray([1, 1, 2]) // => false + * isUniqueArray([{ n: 1 }, { n: 1 }, { n: 2 }], ({ n }) => n) // => false + * ``` + * + * @internal + * @param {B[]} arr + * @param {(d:B)=>T} extractor? extract the value from B + */ +export function isUniqueArray(arr: B[], extractor?: (value: B) => T) { + const values = new Set(); + + return (function() { + return arr.every((v) => { + const value = extractor ? extractor(v) : v; + + if (values.has(value)) { + return false; + } + + values.add(value); + return true; + }); + })(); +} + +/** + * Returns defined value type if not null nor undefined + * + * @internal + */ +export function isDefined(value?: T): value is NonNullable { + return value !== null && value !== undefined; +} + +/** + * Returns defined value type if value from getter function is not null nor undefined + * + * **IMPORTANT**: You must provide an accurate typeCheck function that will filter out _EVERY_ + * item in the array that is not of type `T`. If not, the type check will override the + * type as `T` which may be incorrect. + * + * @internal + */ +export const isDefinedFrom = (typeCheck: (value: RecursivePartial) => boolean) => ( + value?: RecursivePartial, +): value is NonNullable => { + if (value === undefined) { + return false; + } + + try { + return typeCheck(value); + } catch (error) { + return false; + } +}; diff --git a/src/chart_types/xy_chart/tooltip/tooltip.test.ts b/src/chart_types/xy_chart/tooltip/tooltip.test.ts index 0404f85c19..9b0154e14b 100644 --- a/src/chart_types/xy_chart/tooltip/tooltip.test.ts +++ b/src/chart_types/xy_chart/tooltip/tooltip.test.ts @@ -91,6 +91,7 @@ describe('Tooltip formatting', () => { x: 1, y: 10, accessor: 'y1', + mark: null, }, seriesStyle, }; @@ -111,6 +112,7 @@ describe('Tooltip formatting', () => { x: 1, y: 10, accessor: 'y1', + mark: null, }, seriesStyle, }; diff --git a/src/chart_types/xy_chart/tooltip/tooltip.ts b/src/chart_types/xy_chart/tooltip/tooltip.ts index 437565f251..5e37ba762c 100644 --- a/src/chart_types/xy_chart/tooltip/tooltip.ts +++ b/src/chart_types/xy_chart/tooltip/tooltip.ts @@ -39,7 +39,6 @@ export function getHighligthedValues( tooltipValues: TooltipValue[], defaultValue?: string, ): Map { - // map from seriesKey to LegendItemExtraValues const seriesTooltipValues = new Map(); tooltipValues.forEach(({ value, seriesIdentifier, valueAccessor }) => { @@ -64,7 +63,7 @@ export function getHighligthedValues( /** @internal */ export function formatTooltip( - { color, value: { x, y, accessor }, seriesIdentifier }: IndexedGeometry, + { color, value: { x, y, mark, accessor }, seriesIdentifier }: IndexedGeometry, spec: BasicSeriesSpec, isHeader: boolean, isHighlighted: boolean, @@ -83,11 +82,14 @@ export function formatTooltip( const value = isHeader ? x : y; const tickFormatOptions: TickFormatterOptions | undefined = spec.timeZone ? { timeZone: spec.timeZone } : undefined; + const markValue = axisSpec ? axisSpec.tickFormat(mark, tickFormatOptions) : emptyFormatter(mark); + return { seriesIdentifier, valueAccessor: accessor, label, value: axisSpec ? axisSpec.tickFormat(value, tickFormatOptions) : emptyFormatter(value), + markValue: isHeader || mark === null ? null : markValue, color, isHighlighted: isHeader ? false : isHighlighted, isVisible, 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 f22c502dcc..56b68e2753 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 @@ -9,6 +9,7 @@ Array [ "x": 0, "y": 1, }, + "mark": null, "x": 0, "y0": null, "y1": null, @@ -32,6 +33,7 @@ Array [ "x": 1, "y": 2, }, + "mark": null, "x": 1, "y0": null, "y1": null, @@ -55,6 +57,7 @@ Array [ "x": 2, "y": 10, }, + "mark": null, "x": 2, "y0": null, "y1": null, @@ -78,6 +81,7 @@ Array [ "x": 3, "y": 6, }, + "mark": null, "x": 3, "y0": null, "y1": null, @@ -107,6 +111,7 @@ Array [ "x": 0, "y": 1, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -117,6 +122,7 @@ Array [ "x": 0, "y": 2, }, + "mark": null, "x": 0, "y0": null, "y1": 2, @@ -127,6 +133,7 @@ Array [ "x": 1, "y": 2, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -137,6 +144,7 @@ Array [ "x": 1, "y": 3, }, + "mark": null, "x": 1, "y0": null, "y1": 3, @@ -147,6 +155,7 @@ Array [ "x": 2, "y": 3, }, + "mark": null, "x": 2, "y0": null, "y1": 3, @@ -157,6 +166,7 @@ Array [ "x": 2, "y": 4, }, + "mark": null, "x": 2, "y0": null, "y1": 4, @@ -167,6 +177,7 @@ Array [ "x": 3, "y": 4, }, + "mark": null, "x": 3, "y0": null, "y1": 4, @@ -177,6 +188,7 @@ Array [ "x": 3, "y": 5, }, + "mark": null, "x": 3, "y0": null, "y1": 5, @@ -204,6 +216,7 @@ Array [ "x": 0, "y": 1, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -215,6 +228,7 @@ Array [ "x": 1, "y": 2, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -226,6 +240,7 @@ Array [ "x": 2, "y": 1, }, + "mark": null, "x": 2, "y0": null, "y1": 1, @@ -237,6 +252,7 @@ Array [ "x": 3, "y": 6, }, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -264,6 +280,7 @@ Array [ "x": 0, "y": 1, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -275,6 +292,7 @@ Array [ "x": 1, "y": 2, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -286,6 +304,7 @@ Array [ "x": 2, "y": 2, }, + "mark": null, "x": 2, "y0": null, "y1": 2, @@ -297,6 +316,7 @@ Array [ "x": 3, "y": 6, }, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -324,6 +344,7 @@ Array [ "x": 0, "y": 1, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -335,6 +356,7 @@ Array [ "x": 1, "y": 2, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -346,6 +368,7 @@ Array [ "x": 2, "y": 3, }, + "mark": null, "x": 2, "y0": null, "y1": 3, @@ -357,6 +380,7 @@ Array [ "x": 3, "y": 6, }, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -384,6 +408,7 @@ Array [ "x": 0, "y": 1, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -395,6 +420,7 @@ Array [ "x": 1, "y": 2, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -406,6 +432,7 @@ Array [ "x": 2, "y": 4, }, + "mark": null, "x": 2, "y0": null, "y1": 4, @@ -417,6 +444,7 @@ Array [ "x": 3, "y": 6, }, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -448,6 +476,7 @@ Array [ "y1": 1, "y2": 3, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -458,6 +487,7 @@ Array [ "y1": 2, "y2": 7, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -468,6 +498,7 @@ Array [ "y1": 1, "y2": 2, }, + "mark": null, "x": 2, "y0": null, "y1": 1, @@ -478,6 +509,7 @@ Array [ "y1": 6, "y2": 10, }, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -499,6 +531,7 @@ Array [ "y1": 1, "y2": 3, }, + "mark": null, "x": 0, "y0": null, "y1": 3, @@ -509,6 +542,7 @@ Array [ "y1": 2, "y2": 7, }, + "mark": null, "x": 1, "y0": null, "y1": 7, @@ -519,6 +553,7 @@ Array [ "y1": 1, "y2": 2, }, + "mark": null, "x": 2, "y0": null, "y1": 2, @@ -529,6 +564,7 @@ Array [ "y1": 6, "y2": 10, }, + "mark": null, "x": 3, "y0": null, "y1": 10, @@ -556,6 +592,7 @@ Array [ "y1": 1, "y2": 4, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -567,6 +604,7 @@ Array [ "y1": 2, "y2": 1, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -578,6 +616,7 @@ Array [ "y1": 10, "y2": 5, }, + "mark": null, "x": 2, "y0": null, "y1": 10, @@ -589,6 +628,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 3, "y0": null, "y1": 7, @@ -614,6 +654,7 @@ Array [ "y1": 1, "y2": 4, }, + "mark": null, "x": 0, "y0": null, "y1": 4, @@ -625,6 +666,7 @@ Array [ "y1": 2, "y2": 1, }, + "mark": null, "x": 1, "y0": null, "y1": 1, @@ -636,6 +678,7 @@ Array [ "y1": 10, "y2": 5, }, + "mark": null, "x": 2, "y0": null, "y1": 5, @@ -647,6 +690,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 3, "y0": null, "y1": 3, @@ -672,6 +716,7 @@ Array [ "y1": 3, "y2": 6, }, + "mark": null, "x": 0, "y0": null, "y1": 3, @@ -683,6 +728,7 @@ Array [ "y1": 2, "y2": 5, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -694,6 +740,7 @@ Array [ "y1": 3, "y2": 1, }, + "mark": null, "x": 2, "y0": null, "y1": 3, @@ -705,6 +752,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -730,6 +778,7 @@ Array [ "y1": 3, "y2": 6, }, + "mark": null, "x": 0, "y0": null, "y1": 6, @@ -741,6 +790,7 @@ Array [ "y1": 2, "y2": 5, }, + "mark": null, "x": 1, "y0": null, "y1": 5, @@ -752,6 +802,7 @@ Array [ "y1": 3, "y2": 1, }, + "mark": null, "x": 2, "y0": null, "y1": 1, @@ -763,6 +814,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 3, "y0": null, "y1": 4, @@ -794,6 +846,7 @@ Array [ "y1": 1, "y2": 4, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -806,6 +859,7 @@ Array [ "y1": 2, "y2": 1, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -818,6 +872,7 @@ Array [ "y1": 10, "y2": 5, }, + "mark": null, "x": 2, "y0": null, "y1": 10, @@ -830,6 +885,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 3, "y0": null, "y1": 7, @@ -842,6 +898,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 6, "y0": null, "y1": 7, @@ -870,6 +927,7 @@ Array [ "y1": 1, "y2": 4, }, + "mark": null, "x": 0, "y0": null, "y1": 4, @@ -882,6 +940,7 @@ Array [ "y1": 2, "y2": 1, }, + "mark": null, "x": 1, "y0": null, "y1": 1, @@ -894,6 +953,7 @@ Array [ "y1": 10, "y2": 5, }, + "mark": null, "x": 2, "y0": null, "y1": 5, @@ -906,6 +966,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 3, "y0": null, "y1": 3, @@ -918,6 +979,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 6, "y0": null, "y1": 3, @@ -946,6 +1008,7 @@ Array [ "y1": 1, "y2": 4, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -958,6 +1021,7 @@ Array [ "y1": 2, "y2": 1, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -970,6 +1034,7 @@ Array [ "y1": 10, "y2": 5, }, + "mark": null, "x": 2, "y0": null, "y1": 10, @@ -982,6 +1047,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 3, "y0": null, "y1": 7, @@ -994,6 +1060,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 6, "y0": null, "y1": 7, @@ -1022,6 +1089,7 @@ Array [ "y1": 1, "y2": 4, }, + "mark": null, "x": 0, "y0": null, "y1": 4, @@ -1034,6 +1102,7 @@ Array [ "y1": 2, "y2": 1, }, + "mark": null, "x": 1, "y0": null, "y1": 1, @@ -1046,6 +1115,7 @@ Array [ "y1": 10, "y2": 5, }, + "mark": null, "x": 2, "y0": null, "y1": 5, @@ -1058,6 +1128,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 3, "y0": null, "y1": 3, @@ -1070,6 +1141,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 6, "y0": null, "y1": 3, @@ -1098,6 +1170,7 @@ Array [ "y1": 3, "y2": 6, }, + "mark": null, "x": 0, "y0": null, "y1": 3, @@ -1110,6 +1183,7 @@ Array [ "y1": 2, "y2": 5, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -1122,6 +1196,7 @@ Array [ "y1": 3, "y2": 1, }, + "mark": null, "x": 2, "y0": null, "y1": 3, @@ -1134,6 +1209,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -1146,6 +1222,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 6, "y0": null, "y1": 6, @@ -1174,6 +1251,7 @@ Array [ "y1": 3, "y2": 6, }, + "mark": null, "x": 0, "y0": null, "y1": 6, @@ -1186,6 +1264,7 @@ Array [ "y1": 2, "y2": 5, }, + "mark": null, "x": 1, "y0": null, "y1": 5, @@ -1198,6 +1277,7 @@ Array [ "y1": 3, "y2": 1, }, + "mark": null, "x": 2, "y0": null, "y1": 1, @@ -1210,6 +1290,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 3, "y0": null, "y1": 4, @@ -1222,6 +1303,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 6, "y0": null, "y1": 4, @@ -1250,6 +1332,7 @@ Array [ "y1": 3, "y2": 6, }, + "mark": null, "x": 0, "y0": null, "y1": 3, @@ -1262,6 +1345,7 @@ Array [ "y1": 2, "y2": 5, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -1274,6 +1358,7 @@ Array [ "y1": 3, "y2": 1, }, + "mark": null, "x": 2, "y0": null, "y1": 3, @@ -1286,6 +1371,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -1298,6 +1384,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 6, "y0": null, "y1": 6, @@ -1326,6 +1413,7 @@ Array [ "y1": 3, "y2": 6, }, + "mark": null, "x": 0, "y0": null, "y1": 6, @@ -1338,6 +1426,7 @@ Array [ "y1": 2, "y2": 5, }, + "mark": null, "x": 1, "y0": null, "y1": 5, @@ -1350,6 +1439,7 @@ Array [ "y1": 3, "y2": 1, }, + "mark": null, "x": 2, "y0": null, "y1": 1, @@ -1362,6 +1452,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 3, "y0": null, "y1": 4, @@ -1374,6 +1465,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 6, "y0": null, "y1": 4, @@ -1403,6 +1495,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 0, + "mark": null, "x": 0, "y0": null, "y1": 0, @@ -1411,6 +1504,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": null, "y1": 1, @@ -1419,6 +1513,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": null, "y1": 2, @@ -1427,6 +1522,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 3, + "mark": null, "x": 3, "y0": null, "y1": 3, @@ -1435,6 +1531,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": null, "y1": 4, @@ -1443,6 +1540,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 5, + "mark": null, "x": 5, "y0": null, "y1": 5, @@ -1451,6 +1549,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 6, + "mark": null, "x": 6, "y0": null, "y1": 6, @@ -1459,6 +1558,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 7, + "mark": null, "x": 7, "y0": null, "y1": 7, @@ -1467,6 +1567,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 8, + "mark": null, "x": 8, "y0": null, "y1": 8, @@ -1475,6 +1576,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 9, + "mark": null, "x": 9, "y0": null, "y1": 9, @@ -1483,6 +1585,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 10, + "mark": null, "x": 10, "y0": null, "y1": 10, @@ -1491,6 +1594,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 11, + "mark": null, "x": 11, "y0": null, "y1": 11, @@ -1499,6 +1603,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 12, + "mark": null, "x": 12, "y0": null, "y1": 12, @@ -1507,6 +1612,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 13, + "mark": null, "x": 13, "y0": null, "y1": 13, @@ -1515,6 +1621,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 14, + "mark": null, "x": 14, "y0": null, "y1": 14, @@ -1523,6 +1630,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 15, + "mark": null, "x": 15, "y0": null, "y1": 15, @@ -1531,6 +1639,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 16, + "mark": null, "x": 16, "y0": null, "y1": 16, @@ -1539,6 +1648,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 17, + "mark": null, "x": 17, "y0": null, "y1": 17, @@ -1547,6 +1657,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 18, + "mark": null, "x": 18, "y0": null, "y1": 18, @@ -1555,6 +1666,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 19, + "mark": null, "x": 19, "y0": null, "y1": 19, @@ -1563,6 +1675,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 20, + "mark": null, "x": 20, "y0": null, "y1": 20, @@ -1571,6 +1684,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 21, + "mark": null, "x": 21, "y0": null, "y1": 21, @@ -1579,6 +1693,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 22, + "mark": null, "x": 22, "y0": null, "y1": 22, @@ -1587,6 +1702,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 23, + "mark": null, "x": 23, "y0": null, "y1": 23, @@ -1595,6 +1711,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 24, + "mark": null, "x": 24, "y0": null, "y1": 24, @@ -1603,6 +1720,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 25, + "mark": null, "x": 25, "y0": null, "y1": 25, @@ -1611,6 +1729,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 26, + "mark": null, "x": 26, "y0": null, "y1": 26, @@ -1619,6 +1738,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 27, + "mark": null, "x": 27, "y0": null, "y1": 27, @@ -1627,6 +1747,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 28, + "mark": null, "x": 28, "y0": null, "y1": 28, @@ -1635,6 +1756,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 29, + "mark": null, "x": 29, "y0": null, "y1": 29, @@ -1643,6 +1765,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 30, + "mark": null, "x": 30, "y0": null, "y1": 30, @@ -1651,6 +1774,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 31, + "mark": null, "x": 31, "y0": null, "y1": 31, @@ -1659,6 +1783,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 32, + "mark": null, "x": 32, "y0": null, "y1": 32, @@ -1667,6 +1792,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 33, + "mark": null, "x": 33, "y0": null, "y1": 33, @@ -1675,6 +1801,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 34, + "mark": null, "x": 34, "y0": null, "y1": 34, @@ -1683,6 +1810,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 35, + "mark": null, "x": 35, "y0": null, "y1": 35, @@ -1691,6 +1819,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 36, + "mark": null, "x": 36, "y0": null, "y1": 36, @@ -1699,6 +1828,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 37, + "mark": null, "x": 37, "y0": null, "y1": 37, @@ -1707,6 +1837,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 38, + "mark": null, "x": 38, "y0": null, "y1": 38, @@ -1715,6 +1846,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 39, + "mark": null, "x": 39, "y0": null, "y1": 39, @@ -1723,6 +1855,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 40, + "mark": null, "x": 40, "y0": null, "y1": 40, @@ -1731,6 +1864,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 41, + "mark": null, "x": 41, "y0": null, "y1": 41, @@ -1739,6 +1873,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 42, + "mark": null, "x": 42, "y0": null, "y1": 42, @@ -1747,6 +1882,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 43, + "mark": null, "x": 43, "y0": null, "y1": 43, @@ -1755,6 +1891,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 44, + "mark": null, "x": 44, "y0": null, "y1": 44, @@ -1763,6 +1900,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 45, + "mark": null, "x": 45, "y0": null, "y1": 45, @@ -1771,6 +1909,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 46, + "mark": null, "x": 46, "y0": null, "y1": 46, @@ -1779,6 +1918,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 47, + "mark": null, "x": 47, "y0": null, "y1": 47, @@ -1787,6 +1927,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 48, + "mark": null, "x": 48, "y0": null, "y1": 48, @@ -1795,6 +1936,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 49, + "mark": null, "x": 49, "y0": null, "y1": 49, @@ -1803,6 +1945,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 50, + "mark": null, "x": 50, "y0": null, "y1": 50, @@ -1811,6 +1954,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 51, + "mark": null, "x": 51, "y0": null, "y1": 51, @@ -1819,6 +1963,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 52, + "mark": null, "x": 52, "y0": null, "y1": 52, @@ -1827,6 +1972,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 53, + "mark": null, "x": 53, "y0": null, "y1": 53, @@ -1835,6 +1981,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 54, + "mark": null, "x": 54, "y0": null, "y1": 54, @@ -1843,6 +1990,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 55, + "mark": null, "x": 55, "y0": null, "y1": 55, @@ -1851,6 +1999,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 56, + "mark": null, "x": 56, "y0": null, "y1": 56, @@ -1859,6 +2008,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 57, + "mark": null, "x": 57, "y0": null, "y1": 57, @@ -1867,6 +2017,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 58, + "mark": null, "x": 58, "y0": null, "y1": 58, @@ -1875,6 +2026,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 59, + "mark": null, "x": 59, "y0": null, "y1": 59, @@ -1883,6 +2035,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 60, + "mark": null, "x": 60, "y0": null, "y1": 60, @@ -1891,6 +2044,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 61, + "mark": null, "x": 61, "y0": null, "y1": 61, @@ -1899,6 +2053,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 62, + "mark": null, "x": 62, "y0": null, "y1": 62, @@ -1907,6 +2062,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 63, + "mark": null, "x": 63, "y0": null, "y1": 63, @@ -1915,6 +2071,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 64, + "mark": null, "x": 64, "y0": null, "y1": 64, @@ -1923,6 +2080,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 65, + "mark": null, "x": 65, "y0": null, "y1": 65, @@ -1931,6 +2089,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 66, + "mark": null, "x": 66, "y0": null, "y1": 66, @@ -1939,6 +2098,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 67, + "mark": null, "x": 67, "y0": null, "y1": 67, @@ -1947,6 +2107,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 68, + "mark": null, "x": 68, "y0": null, "y1": 68, @@ -1955,6 +2116,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 69, + "mark": null, "x": 69, "y0": null, "y1": 69, @@ -1963,6 +2125,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 70, + "mark": null, "x": 70, "y0": null, "y1": 70, @@ -1971,6 +2134,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 71, + "mark": null, "x": 71, "y0": null, "y1": 71, @@ -1979,6 +2143,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 72, + "mark": null, "x": 72, "y0": null, "y1": 72, @@ -1987,6 +2152,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 73, + "mark": null, "x": 73, "y0": null, "y1": 73, @@ -1995,6 +2161,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 74, + "mark": null, "x": 74, "y0": null, "y1": 74, @@ -2003,6 +2170,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 75, + "mark": null, "x": 75, "y0": null, "y1": 75, @@ -2011,6 +2179,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 76, + "mark": null, "x": 76, "y0": null, "y1": 76, @@ -2019,6 +2188,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 77, + "mark": null, "x": 77, "y0": null, "y1": 77, @@ -2027,6 +2197,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 78, + "mark": null, "x": 78, "y0": null, "y1": 78, @@ -2035,6 +2206,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 79, + "mark": null, "x": 79, "y0": null, "y1": 79, @@ -2043,6 +2215,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 80, + "mark": null, "x": 80, "y0": null, "y1": 80, @@ -2051,6 +2224,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 81, + "mark": null, "x": 81, "y0": null, "y1": 81, @@ -2059,6 +2233,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 82, + "mark": null, "x": 82, "y0": null, "y1": 82, @@ -2067,6 +2242,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 83, + "mark": null, "x": 83, "y0": null, "y1": 83, @@ -2075,6 +2251,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 84, + "mark": null, "x": 84, "y0": null, "y1": 84, @@ -2083,6 +2260,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 85, + "mark": null, "x": 85, "y0": null, "y1": 85, @@ -2091,6 +2269,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 86, + "mark": null, "x": 86, "y0": null, "y1": 86, @@ -2099,6 +2278,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 87, + "mark": null, "x": 87, "y0": null, "y1": 87, @@ -2107,6 +2287,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 88, + "mark": null, "x": 88, "y0": null, "y1": 88, @@ -2115,6 +2296,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 89, + "mark": null, "x": 89, "y0": null, "y1": 89, @@ -2123,6 +2305,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 90, + "mark": null, "x": 90, "y0": null, "y1": 90, @@ -2131,6 +2314,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 91, + "mark": null, "x": 91, "y0": null, "y1": 91, @@ -2139,6 +2323,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 92, + "mark": null, "x": 92, "y0": null, "y1": 92, @@ -2147,6 +2332,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 93, + "mark": null, "x": 93, "y0": null, "y1": 93, @@ -2155,6 +2341,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 94, + "mark": null, "x": 94, "y0": null, "y1": 94, @@ -2163,6 +2350,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 95, + "mark": null, "x": 95, "y0": null, "y1": 95, @@ -2171,6 +2359,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 96, + "mark": null, "x": 96, "y0": null, "y1": 96, @@ -2179,6 +2368,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 97, + "mark": null, "x": 97, "y0": null, "y1": 97, @@ -2187,6 +2377,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 98, + "mark": null, "x": 98, "y0": null, "y1": 98, @@ -2195,6 +2386,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 99, + "mark": null, "x": 99, "y0": null, "y1": 99, @@ -2203,6 +2395,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 100, + "mark": null, "x": 100, "y0": null, "y1": 100, @@ -2211,6 +2404,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 101, + "mark": null, "x": 101, "y0": null, "y1": 101, @@ -2219,6 +2413,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 102, + "mark": null, "x": 102, "y0": null, "y1": 102, @@ -2227,6 +2422,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 103, + "mark": null, "x": 103, "y0": null, "y1": 103, @@ -2235,6 +2431,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 104, + "mark": null, "x": 104, "y0": null, "y1": 104, @@ -2243,6 +2440,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 105, + "mark": null, "x": 105, "y0": null, "y1": 105, @@ -2251,6 +2449,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 106, + "mark": null, "x": 106, "y0": null, "y1": 106, @@ -2259,6 +2458,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 107, + "mark": null, "x": 107, "y0": null, "y1": 107, @@ -2267,6 +2467,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 108, + "mark": null, "x": 108, "y0": null, "y1": 108, @@ -2275,6 +2476,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 109, + "mark": null, "x": 109, "y0": null, "y1": 109, @@ -2283,6 +2485,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 110, + "mark": null, "x": 110, "y0": null, "y1": 110, @@ -2291,6 +2494,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 111, + "mark": null, "x": 111, "y0": null, "y1": 111, @@ -2299,6 +2503,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 112, + "mark": null, "x": 112, "y0": null, "y1": 112, @@ -2307,6 +2512,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 113, + "mark": null, "x": 113, "y0": null, "y1": 113, @@ -2315,6 +2521,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 114, + "mark": null, "x": 114, "y0": null, "y1": 114, @@ -2323,6 +2530,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 115, + "mark": null, "x": 115, "y0": null, "y1": 115, @@ -2331,6 +2539,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 116, + "mark": null, "x": 116, "y0": null, "y1": 116, @@ -2339,6 +2548,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 117, + "mark": null, "x": 117, "y0": null, "y1": 117, @@ -2347,6 +2557,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 118, + "mark": null, "x": 118, "y0": null, "y1": 118, @@ -2355,6 +2566,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 119, + "mark": null, "x": 119, "y0": null, "y1": 119, @@ -2363,6 +2575,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 120, + "mark": null, "x": 120, "y0": null, "y1": 120, @@ -2371,6 +2584,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 121, + "mark": null, "x": 121, "y0": null, "y1": 121, @@ -2379,6 +2593,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 122, + "mark": null, "x": 122, "y0": null, "y1": 122, @@ -2387,6 +2602,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 123, + "mark": null, "x": 123, "y0": null, "y1": 123, @@ -2395,6 +2611,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 124, + "mark": null, "x": 124, "y0": null, "y1": 124, @@ -2403,6 +2620,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 125, + "mark": null, "x": 125, "y0": null, "y1": 125, @@ -2411,6 +2629,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 126, + "mark": null, "x": 126, "y0": null, "y1": 126, @@ -2419,6 +2638,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 127, + "mark": null, "x": 127, "y0": null, "y1": 127, @@ -2427,6 +2647,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 128, + "mark": null, "x": 128, "y0": null, "y1": 128, @@ -2435,6 +2656,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 129, + "mark": null, "x": 129, "y0": null, "y1": 129, @@ -2443,6 +2665,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 130, + "mark": null, "x": 130, "y0": null, "y1": 130, @@ -2451,6 +2674,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 131, + "mark": null, "x": 131, "y0": null, "y1": 131, @@ -2459,6 +2683,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 132, + "mark": null, "x": 132, "y0": null, "y1": 132, @@ -2467,6 +2692,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 133, + "mark": null, "x": 133, "y0": null, "y1": 133, @@ -2475,6 +2701,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 134, + "mark": null, "x": 134, "y0": null, "y1": 134, @@ -2483,6 +2710,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 135, + "mark": null, "x": 135, "y0": null, "y1": 135, @@ -2491,6 +2719,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 136, + "mark": null, "x": 136, "y0": null, "y1": 136, @@ -2499,6 +2728,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 137, + "mark": null, "x": 137, "y0": null, "y1": 137, @@ -2507,6 +2737,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 138, + "mark": null, "x": 138, "y0": null, "y1": 138, @@ -2515,6 +2746,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 139, + "mark": null, "x": 139, "y0": null, "y1": 139, @@ -2523,6 +2755,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 140, + "mark": null, "x": 140, "y0": null, "y1": 140, @@ -2531,6 +2764,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 141, + "mark": null, "x": 141, "y0": null, "y1": 141, @@ -2539,6 +2773,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 142, + "mark": null, "x": 142, "y0": null, "y1": 142, @@ -2547,6 +2782,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 143, + "mark": null, "x": 143, "y0": null, "y1": 143, @@ -2555,6 +2791,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 144, + "mark": null, "x": 144, "y0": null, "y1": 144, @@ -2563,6 +2800,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 145, + "mark": null, "x": 145, "y0": null, "y1": 145, @@ -2571,6 +2809,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 146, + "mark": null, "x": 146, "y0": null, "y1": 146, @@ -2579,6 +2818,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 147, + "mark": null, "x": 147, "y0": null, "y1": 147, @@ -2587,6 +2827,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 148, + "mark": null, "x": 148, "y0": null, "y1": 148, @@ -2595,6 +2836,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 149, + "mark": null, "x": 149, "y0": null, "y1": 149, @@ -2603,6 +2845,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 150, + "mark": null, "x": 150, "y0": null, "y1": 150, @@ -2611,6 +2854,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 151, + "mark": null, "x": 151, "y0": null, "y1": 151, @@ -2619,6 +2863,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 152, + "mark": null, "x": 152, "y0": null, "y1": 152, @@ -2627,6 +2872,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 153, + "mark": null, "x": 153, "y0": null, "y1": 153, @@ -2635,6 +2881,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 154, + "mark": null, "x": 154, "y0": null, "y1": 154, @@ -2643,6 +2890,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 155, + "mark": null, "x": 155, "y0": null, "y1": 155, @@ -2651,6 +2899,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 156, + "mark": null, "x": 156, "y0": null, "y1": 156, @@ -2659,6 +2908,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 157, + "mark": null, "x": 157, "y0": null, "y1": 157, @@ -2667,6 +2917,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 158, + "mark": null, "x": 158, "y0": null, "y1": 158, @@ -2675,6 +2926,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 159, + "mark": null, "x": 159, "y0": null, "y1": 159, @@ -2683,6 +2935,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 160, + "mark": null, "x": 160, "y0": null, "y1": 160, @@ -2691,6 +2944,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 161, + "mark": null, "x": 161, "y0": null, "y1": 161, @@ -2699,6 +2953,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 162, + "mark": null, "x": 162, "y0": null, "y1": 162, @@ -2707,6 +2962,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 163, + "mark": null, "x": 163, "y0": null, "y1": 163, @@ -2715,6 +2971,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 164, + "mark": null, "x": 164, "y0": null, "y1": 164, @@ -2723,6 +2980,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 165, + "mark": null, "x": 165, "y0": null, "y1": 165, @@ -2731,6 +2989,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 166, + "mark": null, "x": 166, "y0": null, "y1": 166, @@ -2739,6 +2998,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 167, + "mark": null, "x": 167, "y0": null, "y1": 167, @@ -2747,6 +3007,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 168, + "mark": null, "x": 168, "y0": null, "y1": 168, @@ -2755,6 +3016,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 169, + "mark": null, "x": 169, "y0": null, "y1": 169, @@ -2763,6 +3025,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 170, + "mark": null, "x": 170, "y0": null, "y1": 170, @@ -2771,6 +3034,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 171, + "mark": null, "x": 171, "y0": null, "y1": 171, @@ -2779,6 +3043,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 172, + "mark": null, "x": 172, "y0": null, "y1": 172, @@ -2787,6 +3052,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 173, + "mark": null, "x": 173, "y0": null, "y1": 173, @@ -2795,6 +3061,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 174, + "mark": null, "x": 174, "y0": null, "y1": 174, @@ -2803,6 +3070,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 175, + "mark": null, "x": 175, "y0": null, "y1": 175, @@ -2811,6 +3079,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 176, + "mark": null, "x": 176, "y0": null, "y1": 176, @@ -2819,6 +3088,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 177, + "mark": null, "x": 177, "y0": null, "y1": 177, @@ -2827,6 +3097,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 178, + "mark": null, "x": 178, "y0": null, "y1": 178, @@ -2835,6 +3106,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 179, + "mark": null, "x": 179, "y0": null, "y1": 179, @@ -2843,6 +3115,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 180, + "mark": null, "x": 180, "y0": null, "y1": 180, @@ -2851,6 +3124,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 181, + "mark": null, "x": 181, "y0": null, "y1": 181, @@ -2859,6 +3133,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 182, + "mark": null, "x": 182, "y0": null, "y1": 182, @@ -2867,6 +3142,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 183, + "mark": null, "x": 183, "y0": null, "y1": 183, @@ -2875,6 +3151,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 184, + "mark": null, "x": 184, "y0": null, "y1": 184, @@ -2883,6 +3160,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 185, + "mark": null, "x": 185, "y0": null, "y1": 185, @@ -2891,6 +3169,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 186, + "mark": null, "x": 186, "y0": null, "y1": 186, @@ -2899,6 +3178,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 187, + "mark": null, "x": 187, "y0": null, "y1": 187, @@ -2907,6 +3187,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 188, + "mark": null, "x": 188, "y0": null, "y1": 188, @@ -2915,6 +3196,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 189, + "mark": null, "x": 189, "y0": null, "y1": 189, @@ -2923,6 +3205,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 190, + "mark": null, "x": 190, "y0": null, "y1": 190, @@ -2931,6 +3214,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 191, + "mark": null, "x": 191, "y0": null, "y1": 191, @@ -2939,6 +3223,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 192, + "mark": null, "x": 192, "y0": null, "y1": 192, @@ -2947,6 +3232,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 193, + "mark": null, "x": 193, "y0": null, "y1": 193, @@ -2955,6 +3241,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 194, + "mark": null, "x": 194, "y0": null, "y1": 194, @@ -2963,6 +3250,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 195, + "mark": null, "x": 195, "y0": null, "y1": 195, @@ -2971,6 +3259,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 196, + "mark": null, "x": 196, "y0": null, "y1": 196, @@ -2979,6 +3268,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 197, + "mark": null, "x": 197, "y0": null, "y1": 197, @@ -2987,6 +3277,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 198, + "mark": null, "x": 198, "y0": null, "y1": 198, @@ -2995,6 +3286,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 199, + "mark": null, "x": 199, "y0": null, "y1": 199, @@ -3003,6 +3295,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 200, + "mark": null, "x": 200, "y0": null, "y1": 200, @@ -3011,6 +3304,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 201, + "mark": null, "x": 201, "y0": null, "y1": 201, @@ -3019,6 +3313,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 202, + "mark": null, "x": 202, "y0": null, "y1": 202, @@ -3027,6 +3322,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 203, + "mark": null, "x": 203, "y0": null, "y1": 203, @@ -3035,6 +3331,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 204, + "mark": null, "x": 204, "y0": null, "y1": 204, @@ -3043,6 +3340,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 205, + "mark": null, "x": 205, "y0": null, "y1": 205, @@ -3051,6 +3349,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 206, + "mark": null, "x": 206, "y0": null, "y1": 206, @@ -3059,6 +3358,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 207, + "mark": null, "x": 207, "y0": null, "y1": 207, @@ -3067,6 +3367,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 208, + "mark": null, "x": 208, "y0": null, "y1": 208, @@ -3075,6 +3376,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 209, + "mark": null, "x": 209, "y0": null, "y1": 209, @@ -3083,6 +3385,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 210, + "mark": null, "x": 210, "y0": null, "y1": 210, @@ -3091,6 +3394,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 211, + "mark": null, "x": 211, "y0": null, "y1": 211, @@ -3099,6 +3403,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 212, + "mark": null, "x": 212, "y0": null, "y1": 212, @@ -3107,6 +3412,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 213, + "mark": null, "x": 213, "y0": null, "y1": 213, @@ -3115,6 +3421,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 214, + "mark": null, "x": 214, "y0": null, "y1": 214, @@ -3123,6 +3430,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 215, + "mark": null, "x": 215, "y0": null, "y1": 215, @@ -3131,6 +3439,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 216, + "mark": null, "x": 216, "y0": null, "y1": 216, @@ -3139,6 +3448,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 217, + "mark": null, "x": 217, "y0": null, "y1": 217, @@ -3147,6 +3457,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 218, + "mark": null, "x": 218, "y0": null, "y1": 218, @@ -3155,6 +3466,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 219, + "mark": null, "x": 219, "y0": null, "y1": 219, @@ -3163,6 +3475,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 220, + "mark": null, "x": 220, "y0": null, "y1": 220, @@ -3171,6 +3484,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 221, + "mark": null, "x": 221, "y0": null, "y1": 221, @@ -3179,6 +3493,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 222, + "mark": null, "x": 222, "y0": null, "y1": 222, @@ -3187,6 +3502,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 223, + "mark": null, "x": 223, "y0": null, "y1": 223, @@ -3195,6 +3511,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 224, + "mark": null, "x": 224, "y0": null, "y1": 224, @@ -3203,6 +3520,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 225, + "mark": null, "x": 225, "y0": null, "y1": 225, @@ -3211,6 +3529,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 226, + "mark": null, "x": 226, "y0": null, "y1": 226, @@ -3219,6 +3538,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 227, + "mark": null, "x": 227, "y0": null, "y1": 227, @@ -3227,6 +3547,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 228, + "mark": null, "x": 228, "y0": null, "y1": 228, @@ -3235,6 +3556,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 229, + "mark": null, "x": 229, "y0": null, "y1": 229, @@ -3243,6 +3565,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 230, + "mark": null, "x": 230, "y0": null, "y1": 230, @@ -3251,6 +3574,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 231, + "mark": null, "x": 231, "y0": null, "y1": 231, @@ -3259,6 +3583,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 232, + "mark": null, "x": 232, "y0": null, "y1": 232, @@ -3267,6 +3592,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 233, + "mark": null, "x": 233, "y0": null, "y1": 233, @@ -3275,6 +3601,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 234, + "mark": null, "x": 234, "y0": null, "y1": 234, @@ -3283,6 +3610,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 235, + "mark": null, "x": 235, "y0": null, "y1": 235, @@ -3291,6 +3619,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 236, + "mark": null, "x": 236, "y0": null, "y1": 236, @@ -3299,6 +3628,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 237, + "mark": null, "x": 237, "y0": null, "y1": 237, @@ -3307,6 +3637,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 238, + "mark": null, "x": 238, "y0": null, "y1": 238, @@ -3315,6 +3646,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 239, + "mark": null, "x": 239, "y0": null, "y1": 239, @@ -3323,6 +3655,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 240, + "mark": null, "x": 240, "y0": null, "y1": 240, @@ -3331,6 +3664,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 241, + "mark": null, "x": 241, "y0": null, "y1": 241, @@ -3339,6 +3673,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 242, + "mark": null, "x": 242, "y0": null, "y1": 242, @@ -3347,6 +3682,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 243, + "mark": null, "x": 243, "y0": null, "y1": 243, @@ -3355,6 +3691,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 244, + "mark": null, "x": 244, "y0": null, "y1": 244, @@ -3363,6 +3700,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 245, + "mark": null, "x": 245, "y0": null, "y1": 245, @@ -3371,6 +3709,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 246, + "mark": null, "x": 246, "y0": null, "y1": 246, @@ -3379,6 +3718,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 247, + "mark": null, "x": 247, "y0": null, "y1": 247, @@ -3387,6 +3727,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 248, + "mark": null, "x": 248, "y0": null, "y1": 248, @@ -3395,6 +3736,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 249, + "mark": null, "x": 249, "y0": null, "y1": 249, @@ -3403,6 +3745,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 250, + "mark": null, "x": 250, "y0": null, "y1": 250, @@ -3411,6 +3754,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 251, + "mark": null, "x": 251, "y0": null, "y1": 251, @@ -3419,6 +3763,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 252, + "mark": null, "x": 252, "y0": null, "y1": 252, @@ -3427,6 +3772,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 253, + "mark": null, "x": 253, "y0": null, "y1": 253, @@ -3435,6 +3781,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 254, + "mark": null, "x": 254, "y0": null, "y1": 254, @@ -3443,6 +3790,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 255, + "mark": null, "x": 255, "y0": null, "y1": 255, @@ -3451,6 +3799,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 256, + "mark": null, "x": 256, "y0": null, "y1": 256, @@ -3459,6 +3808,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 257, + "mark": null, "x": 257, "y0": null, "y1": 257, @@ -3467,6 +3817,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 258, + "mark": null, "x": 258, "y0": null, "y1": 258, @@ -3475,6 +3826,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 259, + "mark": null, "x": 259, "y0": null, "y1": 259, @@ -3483,6 +3835,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 260, + "mark": null, "x": 260, "y0": null, "y1": 260, @@ -3491,6 +3844,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 261, + "mark": null, "x": 261, "y0": null, "y1": 261, @@ -3499,6 +3853,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 262, + "mark": null, "x": 262, "y0": null, "y1": 262, @@ -3507,6 +3862,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 263, + "mark": null, "x": 263, "y0": null, "y1": 263, @@ -3515,6 +3871,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 264, + "mark": null, "x": 264, "y0": null, "y1": 264, @@ -3523,6 +3880,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 265, + "mark": null, "x": 265, "y0": null, "y1": 265, @@ -3531,6 +3889,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 266, + "mark": null, "x": 266, "y0": null, "y1": 266, @@ -3539,6 +3898,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 267, + "mark": null, "x": 267, "y0": null, "y1": 267, @@ -3547,6 +3907,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 268, + "mark": null, "x": 268, "y0": null, "y1": 268, @@ -3555,6 +3916,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 269, + "mark": null, "x": 269, "y0": null, "y1": 269, @@ -3563,6 +3925,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 270, + "mark": null, "x": 270, "y0": null, "y1": 270, @@ -3571,6 +3934,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 271, + "mark": null, "x": 271, "y0": null, "y1": 271, @@ -3579,6 +3943,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 272, + "mark": null, "x": 272, "y0": null, "y1": 272, @@ -3587,6 +3952,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 273, + "mark": null, "x": 273, "y0": null, "y1": 273, @@ -3595,6 +3961,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 274, + "mark": null, "x": 274, "y0": null, "y1": 274, @@ -3603,6 +3970,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 275, + "mark": null, "x": 275, "y0": null, "y1": 275, @@ -3611,6 +3979,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 276, + "mark": null, "x": 276, "y0": null, "y1": 276, @@ -3619,6 +3988,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 277, + "mark": null, "x": 277, "y0": null, "y1": 277, @@ -3627,6 +3997,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 278, + "mark": null, "x": 278, "y0": null, "y1": 278, @@ -3635,6 +4006,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 279, + "mark": null, "x": 279, "y0": null, "y1": 279, @@ -3643,6 +4015,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 280, + "mark": null, "x": 280, "y0": null, "y1": 280, @@ -3651,6 +4024,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 281, + "mark": null, "x": 281, "y0": null, "y1": 281, @@ -3659,6 +4033,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 282, + "mark": null, "x": 282, "y0": null, "y1": 282, @@ -3667,6 +4042,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 283, + "mark": null, "x": 283, "y0": null, "y1": 283, @@ -3675,6 +4051,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 284, + "mark": null, "x": 284, "y0": null, "y1": 284, @@ -3683,6 +4060,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 285, + "mark": null, "x": 285, "y0": null, "y1": 285, @@ -3691,6 +4069,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 286, + "mark": null, "x": 286, "y0": null, "y1": 286, @@ -3699,6 +4078,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 287, + "mark": null, "x": 287, "y0": null, "y1": 287, @@ -3707,6 +4087,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 288, + "mark": null, "x": 288, "y0": null, "y1": 288, @@ -3715,6 +4096,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 289, + "mark": null, "x": 289, "y0": null, "y1": 289, @@ -3723,6 +4105,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 290, + "mark": null, "x": 290, "y0": null, "y1": 290, @@ -3731,6 +4114,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 291, + "mark": null, "x": 291, "y0": null, "y1": 291, @@ -3739,6 +4123,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 292, + "mark": null, "x": 292, "y0": null, "y1": 292, @@ -3747,6 +4132,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 293, + "mark": null, "x": 293, "y0": null, "y1": 293, @@ -3755,6 +4141,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 294, + "mark": null, "x": 294, "y0": null, "y1": 294, @@ -3763,6 +4150,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 295, + "mark": null, "x": 295, "y0": null, "y1": 295, @@ -3771,6 +4159,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 296, + "mark": null, "x": 296, "y0": null, "y1": 296, @@ -3779,6 +4168,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 297, + "mark": null, "x": 297, "y0": null, "y1": 297, @@ -3787,6 +4177,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 298, + "mark": null, "x": 298, "y0": null, "y1": 298, @@ -3795,6 +4186,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 299, + "mark": null, "x": 299, "y0": null, "y1": 299, @@ -3803,6 +4195,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 300, + "mark": null, "x": 300, "y0": null, "y1": 300, @@ -3811,6 +4204,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 301, + "mark": null, "x": 301, "y0": null, "y1": 301, @@ -3819,6 +4213,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 302, + "mark": null, "x": 302, "y0": null, "y1": 302, @@ -3827,6 +4222,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 303, + "mark": null, "x": 303, "y0": null, "y1": 303, @@ -3835,6 +4231,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 304, + "mark": null, "x": 304, "y0": null, "y1": 304, @@ -3843,6 +4240,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 305, + "mark": null, "x": 305, "y0": null, "y1": 305, @@ -3851,6 +4249,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 306, + "mark": null, "x": 306, "y0": null, "y1": 306, @@ -3859,6 +4258,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 307, + "mark": null, "x": 307, "y0": null, "y1": 307, @@ -3867,6 +4267,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 308, + "mark": null, "x": 308, "y0": null, "y1": 308, @@ -3875,6 +4276,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 309, + "mark": null, "x": 309, "y0": null, "y1": 309, @@ -3883,6 +4285,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 310, + "mark": null, "x": 310, "y0": null, "y1": 310, @@ -3891,6 +4294,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 311, + "mark": null, "x": 311, "y0": null, "y1": 311, @@ -3899,6 +4303,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 312, + "mark": null, "x": 312, "y0": null, "y1": 312, @@ -3907,6 +4312,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 313, + "mark": null, "x": 313, "y0": null, "y1": 313, @@ -3915,6 +4321,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 314, + "mark": null, "x": 314, "y0": null, "y1": 314, @@ -3923,6 +4330,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 315, + "mark": null, "x": 315, "y0": null, "y1": 315, @@ -3931,6 +4339,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 316, + "mark": null, "x": 316, "y0": null, "y1": 316, @@ -3939,6 +4348,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 317, + "mark": null, "x": 317, "y0": null, "y1": 317, @@ -3947,6 +4357,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 318, + "mark": null, "x": 318, "y0": null, "y1": 318, @@ -3955,6 +4366,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 319, + "mark": null, "x": 319, "y0": null, "y1": 319, @@ -3963,6 +4375,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 320, + "mark": null, "x": 320, "y0": null, "y1": 320, @@ -3971,6 +4384,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 321, + "mark": null, "x": 321, "y0": null, "y1": 321, @@ -3979,6 +4393,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 322, + "mark": null, "x": 322, "y0": null, "y1": 322, @@ -3987,6 +4402,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 323, + "mark": null, "x": 323, "y0": null, "y1": 323, @@ -3995,6 +4411,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 324, + "mark": null, "x": 324, "y0": null, "y1": 324, @@ -4003,6 +4420,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 325, + "mark": null, "x": 325, "y0": null, "y1": 325, @@ -4011,6 +4429,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 326, + "mark": null, "x": 326, "y0": null, "y1": 326, @@ -4019,6 +4438,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 327, + "mark": null, "x": 327, "y0": null, "y1": 327, @@ -4027,6 +4447,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 328, + "mark": null, "x": 328, "y0": null, "y1": 328, @@ -4035,6 +4456,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 329, + "mark": null, "x": 329, "y0": null, "y1": 329, @@ -4043,6 +4465,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 330, + "mark": null, "x": 330, "y0": null, "y1": 330, @@ -4051,6 +4474,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 331, + "mark": null, "x": 331, "y0": null, "y1": 331, @@ -4059,6 +4483,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 332, + "mark": null, "x": 332, "y0": null, "y1": 332, @@ -4067,6 +4492,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 333, + "mark": null, "x": 333, "y0": null, "y1": 333, @@ -4075,6 +4501,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 334, + "mark": null, "x": 334, "y0": null, "y1": 334, @@ -4083,6 +4510,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 335, + "mark": null, "x": 335, "y0": null, "y1": 335, @@ -4091,6 +4519,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 336, + "mark": null, "x": 336, "y0": null, "y1": 336, @@ -4099,6 +4528,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 337, + "mark": null, "x": 337, "y0": null, "y1": 337, @@ -4107,6 +4537,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 338, + "mark": null, "x": 338, "y0": null, "y1": 338, @@ -4115,6 +4546,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 339, + "mark": null, "x": 339, "y0": null, "y1": 339, @@ -4123,6 +4555,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 340, + "mark": null, "x": 340, "y0": null, "y1": 340, @@ -4131,6 +4564,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 341, + "mark": null, "x": 341, "y0": null, "y1": 341, @@ -4139,6 +4573,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 342, + "mark": null, "x": 342, "y0": null, "y1": 342, @@ -4147,6 +4582,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 343, + "mark": null, "x": 343, "y0": null, "y1": 343, @@ -4155,6 +4591,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 344, + "mark": null, "x": 344, "y0": null, "y1": 344, @@ -4163,6 +4600,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 345, + "mark": null, "x": 345, "y0": null, "y1": 345, @@ -4171,6 +4609,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 346, + "mark": null, "x": 346, "y0": null, "y1": 346, @@ -4179,6 +4618,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 347, + "mark": null, "x": 347, "y0": null, "y1": 347, @@ -4187,6 +4627,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 348, + "mark": null, "x": 348, "y0": null, "y1": 348, @@ -4195,6 +4636,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 349, + "mark": null, "x": 349, "y0": null, "y1": 349, @@ -4203,6 +4645,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 350, + "mark": null, "x": 350, "y0": null, "y1": 350, @@ -4211,6 +4654,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 351, + "mark": null, "x": 351, "y0": null, "y1": 351, @@ -4219,6 +4663,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 352, + "mark": null, "x": 352, "y0": null, "y1": 352, @@ -4227,6 +4672,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 353, + "mark": null, "x": 353, "y0": null, "y1": 353, @@ -4235,6 +4681,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 354, + "mark": null, "x": 354, "y0": null, "y1": 354, @@ -4243,6 +4690,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 355, + "mark": null, "x": 355, "y0": null, "y1": 355, @@ -4251,6 +4699,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 356, + "mark": null, "x": 356, "y0": null, "y1": 356, @@ -4259,6 +4708,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 357, + "mark": null, "x": 357, "y0": null, "y1": 357, @@ -4267,6 +4717,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 358, + "mark": null, "x": 358, "y0": null, "y1": 358, @@ -4275,6 +4726,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 359, + "mark": null, "x": 359, "y0": null, "y1": 359, @@ -4283,6 +4735,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 360, + "mark": null, "x": 360, "y0": null, "y1": 360, @@ -4291,6 +4744,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 361, + "mark": null, "x": 361, "y0": null, "y1": 361, @@ -4299,6 +4753,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 362, + "mark": null, "x": 362, "y0": null, "y1": 362, @@ -4307,6 +4762,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 363, + "mark": null, "x": 363, "y0": null, "y1": 363, @@ -4315,6 +4771,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 364, + "mark": null, "x": 364, "y0": null, "y1": 364, @@ -4323,6 +4780,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 365, + "mark": null, "x": 365, "y0": null, "y1": 365, @@ -4331,6 +4789,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 366, + "mark": null, "x": 366, "y0": null, "y1": 366, @@ -4339,6 +4798,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 367, + "mark": null, "x": 367, "y0": null, "y1": 367, @@ -4347,6 +4807,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 368, + "mark": null, "x": 368, "y0": null, "y1": 368, @@ -4355,6 +4816,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 369, + "mark": null, "x": 369, "y0": null, "y1": 369, @@ -4363,6 +4825,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 370, + "mark": null, "x": 370, "y0": null, "y1": 370, @@ -4371,6 +4834,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 371, + "mark": null, "x": 371, "y0": null, "y1": 371, @@ -4379,6 +4843,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 372, + "mark": null, "x": 372, "y0": null, "y1": 372, @@ -4387,6 +4852,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 373, + "mark": null, "x": 373, "y0": null, "y1": 373, @@ -4395,6 +4861,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 374, + "mark": null, "x": 374, "y0": null, "y1": 374, @@ -4403,6 +4870,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 375, + "mark": null, "x": 375, "y0": null, "y1": 375, @@ -4411,6 +4879,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 376, + "mark": null, "x": 376, "y0": null, "y1": 376, @@ -4419,6 +4888,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 377, + "mark": null, "x": 377, "y0": null, "y1": 377, @@ -4427,6 +4897,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 378, + "mark": null, "x": 378, "y0": null, "y1": 378, @@ -4435,6 +4906,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 379, + "mark": null, "x": 379, "y0": null, "y1": 379, @@ -4443,6 +4915,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 380, + "mark": null, "x": 380, "y0": null, "y1": 380, @@ -4451,6 +4924,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 381, + "mark": null, "x": 381, "y0": null, "y1": 381, @@ -4459,6 +4933,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 382, + "mark": null, "x": 382, "y0": null, "y1": 382, @@ -4467,6 +4942,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 383, + "mark": null, "x": 383, "y0": null, "y1": 383, @@ -4475,6 +4951,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 384, + "mark": null, "x": 384, "y0": null, "y1": 384, @@ -4483,6 +4960,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 385, + "mark": null, "x": 385, "y0": null, "y1": 385, @@ -4491,6 +4969,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 386, + "mark": null, "x": 386, "y0": null, "y1": 386, @@ -4499,6 +4978,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 387, + "mark": null, "x": 387, "y0": null, "y1": 387, @@ -4507,6 +4987,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 388, + "mark": null, "x": 388, "y0": null, "y1": 388, @@ -4515,6 +4996,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 389, + "mark": null, "x": 389, "y0": null, "y1": 389, @@ -4523,6 +5005,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 390, + "mark": null, "x": 390, "y0": null, "y1": 390, @@ -4531,6 +5014,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 391, + "mark": null, "x": 391, "y0": null, "y1": 391, @@ -4539,6 +5023,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 392, + "mark": null, "x": 392, "y0": null, "y1": 392, @@ -4547,6 +5032,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 393, + "mark": null, "x": 393, "y0": null, "y1": 393, @@ -4555,6 +5041,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 394, + "mark": null, "x": 394, "y0": null, "y1": 394, @@ -4563,6 +5050,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 395, + "mark": null, "x": 395, "y0": null, "y1": 395, @@ -4571,6 +5059,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 396, + "mark": null, "x": 396, "y0": null, "y1": 396, @@ -4579,6 +5068,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 397, + "mark": null, "x": 397, "y0": null, "y1": 397, @@ -4587,6 +5077,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 398, + "mark": null, "x": 398, "y0": null, "y1": 398, @@ -4595,6 +5086,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 399, + "mark": null, "x": 399, "y0": null, "y1": 399, @@ -4603,6 +5095,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 400, + "mark": null, "x": 400, "y0": null, "y1": 400, @@ -4611,6 +5104,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 401, + "mark": null, "x": 401, "y0": null, "y1": 401, @@ -4619,6 +5113,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 402, + "mark": null, "x": 402, "y0": null, "y1": 402, @@ -4627,6 +5122,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 403, + "mark": null, "x": 403, "y0": null, "y1": 403, @@ -4635,6 +5131,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 404, + "mark": null, "x": 404, "y0": null, "y1": 404, @@ -4643,6 +5140,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 405, + "mark": null, "x": 405, "y0": null, "y1": 405, @@ -4651,6 +5149,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 406, + "mark": null, "x": 406, "y0": null, "y1": 406, @@ -4659,6 +5158,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 407, + "mark": null, "x": 407, "y0": null, "y1": 407, @@ -4667,6 +5167,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 408, + "mark": null, "x": 408, "y0": null, "y1": 408, @@ -4675,6 +5176,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 409, + "mark": null, "x": 409, "y0": null, "y1": 409, @@ -4683,6 +5185,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 410, + "mark": null, "x": 410, "y0": null, "y1": 410, @@ -4691,6 +5194,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 411, + "mark": null, "x": 411, "y0": null, "y1": 411, @@ -4699,6 +5203,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 412, + "mark": null, "x": 412, "y0": null, "y1": 412, @@ -4707,6 +5212,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 413, + "mark": null, "x": 413, "y0": null, "y1": 413, @@ -4715,6 +5221,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 414, + "mark": null, "x": 414, "y0": null, "y1": 414, @@ -4723,6 +5230,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 415, + "mark": null, "x": 415, "y0": null, "y1": 415, @@ -4731,6 +5239,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 416, + "mark": null, "x": 416, "y0": null, "y1": 416, @@ -4739,6 +5248,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 417, + "mark": null, "x": 417, "y0": null, "y1": 417, @@ -4747,6 +5257,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 418, + "mark": null, "x": 418, "y0": null, "y1": 418, @@ -4755,6 +5266,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 419, + "mark": null, "x": 419, "y0": null, "y1": 419, @@ -4763,6 +5275,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 420, + "mark": null, "x": 420, "y0": null, "y1": 420, @@ -4771,6 +5284,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 421, + "mark": null, "x": 421, "y0": null, "y1": 421, @@ -4779,6 +5293,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 422, + "mark": null, "x": 422, "y0": null, "y1": 422, @@ -4787,6 +5302,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 423, + "mark": null, "x": 423, "y0": null, "y1": 423, @@ -4795,6 +5311,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 424, + "mark": null, "x": 424, "y0": null, "y1": 424, @@ -4803,6 +5320,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 425, + "mark": null, "x": 425, "y0": null, "y1": 425, @@ -4811,6 +5329,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 426, + "mark": null, "x": 426, "y0": null, "y1": 426, @@ -4819,6 +5338,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 427, + "mark": null, "x": 427, "y0": null, "y1": 427, @@ -4827,6 +5347,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 428, + "mark": null, "x": 428, "y0": null, "y1": 428, @@ -4835,6 +5356,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 429, + "mark": null, "x": 429, "y0": null, "y1": 429, @@ -4843,6 +5365,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 430, + "mark": null, "x": 430, "y0": null, "y1": 430, @@ -4851,6 +5374,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 431, + "mark": null, "x": 431, "y0": null, "y1": 431, @@ -4859,6 +5383,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 432, + "mark": null, "x": 432, "y0": null, "y1": 432, @@ -4867,6 +5392,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 433, + "mark": null, "x": 433, "y0": null, "y1": 433, @@ -4875,6 +5401,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 434, + "mark": null, "x": 434, "y0": null, "y1": 434, @@ -4883,6 +5410,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 435, + "mark": null, "x": 435, "y0": null, "y1": 435, @@ -4891,6 +5419,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 436, + "mark": null, "x": 436, "y0": null, "y1": 436, @@ -4899,6 +5428,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 437, + "mark": null, "x": 437, "y0": null, "y1": 437, @@ -4907,6 +5437,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 438, + "mark": null, "x": 438, "y0": null, "y1": 438, @@ -4915,6 +5446,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 439, + "mark": null, "x": 439, "y0": null, "y1": 439, @@ -4923,6 +5455,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 440, + "mark": null, "x": 440, "y0": null, "y1": 440, @@ -4931,6 +5464,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 441, + "mark": null, "x": 441, "y0": null, "y1": 441, @@ -4939,6 +5473,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 442, + "mark": null, "x": 442, "y0": null, "y1": 442, @@ -4947,6 +5482,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 443, + "mark": null, "x": 443, "y0": null, "y1": 443, @@ -4955,6 +5491,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 444, + "mark": null, "x": 444, "y0": null, "y1": 444, @@ -4963,6 +5500,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 445, + "mark": null, "x": 445, "y0": null, "y1": 445, @@ -4971,6 +5509,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 446, + "mark": null, "x": 446, "y0": null, "y1": 446, @@ -4979,6 +5518,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 447, + "mark": null, "x": 447, "y0": null, "y1": 447, @@ -4987,6 +5527,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 448, + "mark": null, "x": 448, "y0": null, "y1": 448, @@ -4995,6 +5536,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 449, + "mark": null, "x": 449, "y0": null, "y1": 449, @@ -5003,6 +5545,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 450, + "mark": null, "x": 450, "y0": null, "y1": 450, @@ -5011,6 +5554,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 451, + "mark": null, "x": 451, "y0": null, "y1": 451, @@ -5019,6 +5563,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 452, + "mark": null, "x": 452, "y0": null, "y1": 452, @@ -5027,6 +5572,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 453, + "mark": null, "x": 453, "y0": null, "y1": 453, @@ -5035,6 +5581,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 454, + "mark": null, "x": 454, "y0": null, "y1": 454, @@ -5043,6 +5590,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 455, + "mark": null, "x": 455, "y0": null, "y1": 455, @@ -5051,6 +5599,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 456, + "mark": null, "x": 456, "y0": null, "y1": 456, @@ -5059,6 +5608,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 457, + "mark": null, "x": 457, "y0": null, "y1": 457, @@ -5067,6 +5617,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 458, + "mark": null, "x": 458, "y0": null, "y1": 458, @@ -5075,6 +5626,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 459, + "mark": null, "x": 459, "y0": null, "y1": 459, @@ -5083,6 +5635,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 460, + "mark": null, "x": 460, "y0": null, "y1": 460, @@ -5091,6 +5644,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 461, + "mark": null, "x": 461, "y0": null, "y1": 461, @@ -5099,6 +5653,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 462, + "mark": null, "x": 462, "y0": null, "y1": 462, @@ -5107,6 +5662,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 463, + "mark": null, "x": 463, "y0": null, "y1": 463, @@ -5115,6 +5671,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 464, + "mark": null, "x": 464, "y0": null, "y1": 464, @@ -5123,6 +5680,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 465, + "mark": null, "x": 465, "y0": null, "y1": 465, @@ -5131,6 +5689,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 466, + "mark": null, "x": 466, "y0": null, "y1": 466, @@ -5139,6 +5698,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 467, + "mark": null, "x": 467, "y0": null, "y1": 467, @@ -5147,6 +5707,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 468, + "mark": null, "x": 468, "y0": null, "y1": 468, @@ -5155,6 +5716,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 469, + "mark": null, "x": 469, "y0": null, "y1": 469, @@ -5163,6 +5725,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 470, + "mark": null, "x": 470, "y0": null, "y1": 470, @@ -5171,6 +5734,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 471, + "mark": null, "x": 471, "y0": null, "y1": 471, @@ -5179,6 +5743,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 472, + "mark": null, "x": 472, "y0": null, "y1": 472, @@ -5187,6 +5752,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 473, + "mark": null, "x": 473, "y0": null, "y1": 473, @@ -5195,6 +5761,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 474, + "mark": null, "x": 474, "y0": null, "y1": 474, @@ -5203,6 +5770,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 475, + "mark": null, "x": 475, "y0": null, "y1": 475, @@ -5211,6 +5779,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 476, + "mark": null, "x": 476, "y0": null, "y1": 476, @@ -5219,6 +5788,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 477, + "mark": null, "x": 477, "y0": null, "y1": 477, @@ -5227,6 +5797,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 478, + "mark": null, "x": 478, "y0": null, "y1": 478, @@ -5235,6 +5806,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 479, + "mark": null, "x": 479, "y0": null, "y1": 479, @@ -5243,6 +5815,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 480, + "mark": null, "x": 480, "y0": null, "y1": 480, @@ -5251,6 +5824,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 481, + "mark": null, "x": 481, "y0": null, "y1": 481, @@ -5259,6 +5833,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 482, + "mark": null, "x": 482, "y0": null, "y1": 482, @@ -5267,6 +5842,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 483, + "mark": null, "x": 483, "y0": null, "y1": 483, @@ -5275,6 +5851,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 484, + "mark": null, "x": 484, "y0": null, "y1": 484, @@ -5283,6 +5860,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 485, + "mark": null, "x": 485, "y0": null, "y1": 485, @@ -5291,6 +5869,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 486, + "mark": null, "x": 486, "y0": null, "y1": 486, @@ -5299,6 +5878,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 487, + "mark": null, "x": 487, "y0": null, "y1": 487, @@ -5307,6 +5887,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 488, + "mark": null, "x": 488, "y0": null, "y1": 488, @@ -5315,6 +5896,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 489, + "mark": null, "x": 489, "y0": null, "y1": 489, @@ -5323,6 +5905,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 490, + "mark": null, "x": 490, "y0": null, "y1": 490, @@ -5331,6 +5914,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 491, + "mark": null, "x": 491, "y0": null, "y1": 491, @@ -5339,6 +5923,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 492, + "mark": null, "x": 492, "y0": null, "y1": 492, @@ -5347,6 +5932,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 493, + "mark": null, "x": 493, "y0": null, "y1": 493, @@ -5355,6 +5941,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 494, + "mark": null, "x": 494, "y0": null, "y1": 494, @@ -5363,6 +5950,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 495, + "mark": null, "x": 495, "y0": null, "y1": 495, @@ -5371,6 +5959,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 496, + "mark": null, "x": 496, "y0": null, "y1": 496, @@ -5379,6 +5968,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 497, + "mark": null, "x": 497, "y0": null, "y1": 497, @@ -5387,6 +5977,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 498, + "mark": null, "x": 498, "y0": null, "y1": 498, @@ -5395,6 +5986,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 499, + "mark": null, "x": 499, "y0": null, "y1": 499, @@ -5403,6 +5995,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 500, + "mark": null, "x": 500, "y0": null, "y1": 500, @@ -5411,6 +6004,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 501, + "mark": null, "x": 501, "y0": null, "y1": 501, @@ -5419,6 +6013,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 502, + "mark": null, "x": 502, "y0": null, "y1": 502, @@ -5427,6 +6022,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 503, + "mark": null, "x": 503, "y0": null, "y1": 503, @@ -5435,6 +6031,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 504, + "mark": null, "x": 504, "y0": null, "y1": 504, @@ -5443,6 +6040,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 505, + "mark": null, "x": 505, "y0": null, "y1": 505, @@ -5451,6 +6049,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 506, + "mark": null, "x": 506, "y0": null, "y1": 506, @@ -5459,6 +6058,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 507, + "mark": null, "x": 507, "y0": null, "y1": 507, @@ -5467,6 +6067,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 508, + "mark": null, "x": 508, "y0": null, "y1": 508, @@ -5475,6 +6076,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 509, + "mark": null, "x": 509, "y0": null, "y1": 509, @@ -5483,6 +6085,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 510, + "mark": null, "x": 510, "y0": null, "y1": 510, @@ -5491,6 +6094,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 511, + "mark": null, "x": 511, "y0": null, "y1": 511, @@ -5499,6 +6103,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 512, + "mark": null, "x": 512, "y0": null, "y1": 512, @@ -5507,6 +6112,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 513, + "mark": null, "x": 513, "y0": null, "y1": 513, @@ -5515,6 +6121,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 514, + "mark": null, "x": 514, "y0": null, "y1": 514, @@ -5523,6 +6130,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 515, + "mark": null, "x": 515, "y0": null, "y1": 515, @@ -5531,6 +6139,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 516, + "mark": null, "x": 516, "y0": null, "y1": 516, @@ -5539,6 +6148,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 517, + "mark": null, "x": 517, "y0": null, "y1": 517, @@ -5547,6 +6157,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 518, + "mark": null, "x": 518, "y0": null, "y1": 518, @@ -5555,6 +6166,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 519, + "mark": null, "x": 519, "y0": null, "y1": 519, @@ -5563,6 +6175,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 520, + "mark": null, "x": 520, "y0": null, "y1": 520, @@ -5571,6 +6184,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 521, + "mark": null, "x": 521, "y0": null, "y1": 521, @@ -5579,6 +6193,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 522, + "mark": null, "x": 522, "y0": null, "y1": 522, @@ -5587,6 +6202,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 523, + "mark": null, "x": 523, "y0": null, "y1": 523, @@ -5595,6 +6211,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 524, + "mark": null, "x": 524, "y0": null, "y1": 524, @@ -5603,6 +6220,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 525, + "mark": null, "x": 525, "y0": null, "y1": 525, @@ -5611,6 +6229,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 526, + "mark": null, "x": 526, "y0": null, "y1": 526, @@ -5619,6 +6238,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 527, + "mark": null, "x": 527, "y0": null, "y1": 527, @@ -5627,6 +6247,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 528, + "mark": null, "x": 528, "y0": null, "y1": 528, @@ -5635,6 +6256,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 529, + "mark": null, "x": 529, "y0": null, "y1": 529, @@ -5643,6 +6265,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 530, + "mark": null, "x": 530, "y0": null, "y1": 530, @@ -5651,6 +6274,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 531, + "mark": null, "x": 531, "y0": null, "y1": 531, @@ -5659,6 +6283,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 532, + "mark": null, "x": 532, "y0": null, "y1": 532, @@ -5667,6 +6292,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 533, + "mark": null, "x": 533, "y0": null, "y1": 533, @@ -5675,6 +6301,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 534, + "mark": null, "x": 534, "y0": null, "y1": 534, @@ -5683,6 +6310,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 535, + "mark": null, "x": 535, "y0": null, "y1": 535, @@ -5691,6 +6319,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 536, + "mark": null, "x": 536, "y0": null, "y1": 536, @@ -5699,6 +6328,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 537, + "mark": null, "x": 537, "y0": null, "y1": 537, @@ -5707,6 +6337,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 538, + "mark": null, "x": 538, "y0": null, "y1": 538, @@ -5715,6 +6346,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 539, + "mark": null, "x": 539, "y0": null, "y1": 539, @@ -5723,6 +6355,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 540, + "mark": null, "x": 540, "y0": null, "y1": 540, @@ -5731,6 +6364,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 541, + "mark": null, "x": 541, "y0": null, "y1": 541, @@ -5739,6 +6373,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 542, + "mark": null, "x": 542, "y0": null, "y1": 542, @@ -5747,6 +6382,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 543, + "mark": null, "x": 543, "y0": null, "y1": 543, @@ -5755,6 +6391,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 544, + "mark": null, "x": 544, "y0": null, "y1": 544, @@ -5763,6 +6400,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 545, + "mark": null, "x": 545, "y0": null, "y1": 545, @@ -5771,6 +6409,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 546, + "mark": null, "x": 546, "y0": null, "y1": 546, @@ -5779,6 +6418,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 547, + "mark": null, "x": 547, "y0": null, "y1": 547, @@ -5787,6 +6427,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 548, + "mark": null, "x": 548, "y0": null, "y1": 548, @@ -5795,6 +6436,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 549, + "mark": null, "x": 549, "y0": null, "y1": 549, @@ -5803,6 +6445,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 550, + "mark": null, "x": 550, "y0": null, "y1": 550, @@ -5811,6 +6454,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 551, + "mark": null, "x": 551, "y0": null, "y1": 551, @@ -5819,6 +6463,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 552, + "mark": null, "x": 552, "y0": null, "y1": 552, @@ -5827,6 +6472,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 553, + "mark": null, "x": 553, "y0": null, "y1": 553, @@ -5835,6 +6481,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 554, + "mark": null, "x": 554, "y0": null, "y1": 554, @@ -5843,6 +6490,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 555, + "mark": null, "x": 555, "y0": null, "y1": 555, @@ -5851,6 +6499,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 556, + "mark": null, "x": 556, "y0": null, "y1": 556, @@ -5859,6 +6508,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 557, + "mark": null, "x": 557, "y0": null, "y1": 557, @@ -5867,6 +6517,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 558, + "mark": null, "x": 558, "y0": null, "y1": 558, @@ -5875,6 +6526,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 559, + "mark": null, "x": 559, "y0": null, "y1": 559, @@ -5883,6 +6535,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 560, + "mark": null, "x": 560, "y0": null, "y1": 560, @@ -5891,6 +6544,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 561, + "mark": null, "x": 561, "y0": null, "y1": 561, @@ -5899,6 +6553,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 562, + "mark": null, "x": 562, "y0": null, "y1": 562, @@ -5907,6 +6562,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 563, + "mark": null, "x": 563, "y0": null, "y1": 563, @@ -5915,6 +6571,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 564, + "mark": null, "x": 564, "y0": null, "y1": 564, @@ -5923,6 +6580,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 565, + "mark": null, "x": 565, "y0": null, "y1": 565, @@ -5931,6 +6589,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 566, + "mark": null, "x": 566, "y0": null, "y1": 566, @@ -5939,6 +6598,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 567, + "mark": null, "x": 567, "y0": null, "y1": 567, @@ -5947,6 +6607,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 568, + "mark": null, "x": 568, "y0": null, "y1": 568, @@ -5955,6 +6616,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 569, + "mark": null, "x": 569, "y0": null, "y1": 569, @@ -5963,6 +6625,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 570, + "mark": null, "x": 570, "y0": null, "y1": 570, @@ -5971,6 +6634,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 571, + "mark": null, "x": 571, "y0": null, "y1": 571, @@ -5979,6 +6643,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 572, + "mark": null, "x": 572, "y0": null, "y1": 572, @@ -5987,6 +6652,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 573, + "mark": null, "x": 573, "y0": null, "y1": 573, @@ -5995,6 +6661,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 574, + "mark": null, "x": 574, "y0": null, "y1": 574, @@ -6003,6 +6670,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 575, + "mark": null, "x": 575, "y0": null, "y1": 575, @@ -6011,6 +6679,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 576, + "mark": null, "x": 576, "y0": null, "y1": 576, @@ -6019,6 +6688,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 577, + "mark": null, "x": 577, "y0": null, "y1": 577, @@ -6027,6 +6697,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 578, + "mark": null, "x": 578, "y0": null, "y1": 578, @@ -6035,6 +6706,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 579, + "mark": null, "x": 579, "y0": null, "y1": 579, @@ -6043,6 +6715,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 580, + "mark": null, "x": 580, "y0": null, "y1": 580, @@ -6051,6 +6724,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 581, + "mark": null, "x": 581, "y0": null, "y1": 581, @@ -6059,6 +6733,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 582, + "mark": null, "x": 582, "y0": null, "y1": 582, @@ -6067,6 +6742,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 583, + "mark": null, "x": 583, "y0": null, "y1": 583, @@ -6075,6 +6751,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 584, + "mark": null, "x": 584, "y0": null, "y1": 584, @@ -6083,6 +6760,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 585, + "mark": null, "x": 585, "y0": null, "y1": 585, @@ -6091,6 +6769,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 586, + "mark": null, "x": 586, "y0": null, "y1": 586, @@ -6099,6 +6778,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 587, + "mark": null, "x": 587, "y0": null, "y1": 587, @@ -6107,6 +6787,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 588, + "mark": null, "x": 588, "y0": null, "y1": 588, @@ -6115,6 +6796,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 589, + "mark": null, "x": 589, "y0": null, "y1": 589, @@ -6123,6 +6805,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 590, + "mark": null, "x": 590, "y0": null, "y1": 590, @@ -6131,6 +6814,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 591, + "mark": null, "x": 591, "y0": null, "y1": 591, @@ -6139,6 +6823,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 592, + "mark": null, "x": 592, "y0": null, "y1": 592, @@ -6147,6 +6832,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 593, + "mark": null, "x": 593, "y0": null, "y1": 593, @@ -6155,6 +6841,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 594, + "mark": null, "x": 594, "y0": null, "y1": 594, @@ -6163,6 +6850,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 595, + "mark": null, "x": 595, "y0": null, "y1": 595, @@ -6171,6 +6859,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 596, + "mark": null, "x": 596, "y0": null, "y1": 596, @@ -6179,6 +6868,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 597, + "mark": null, "x": 597, "y0": null, "y1": 597, @@ -6187,6 +6877,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 598, + "mark": null, "x": 598, "y0": null, "y1": 598, @@ -6195,6 +6886,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 599, + "mark": null, "x": 599, "y0": null, "y1": 599, @@ -6203,6 +6895,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 600, + "mark": null, "x": 600, "y0": null, "y1": 600, @@ -6211,6 +6904,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 601, + "mark": null, "x": 601, "y0": null, "y1": 601, @@ -6219,6 +6913,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 602, + "mark": null, "x": 602, "y0": null, "y1": 602, @@ -6227,6 +6922,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 603, + "mark": null, "x": 603, "y0": null, "y1": 603, @@ -6235,6 +6931,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 604, + "mark": null, "x": 604, "y0": null, "y1": 604, @@ -6243,6 +6940,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 605, + "mark": null, "x": 605, "y0": null, "y1": 605, @@ -6251,6 +6949,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 606, + "mark": null, "x": 606, "y0": null, "y1": 606, @@ -6259,6 +6958,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 607, + "mark": null, "x": 607, "y0": null, "y1": 607, @@ -6267,6 +6967,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 608, + "mark": null, "x": 608, "y0": null, "y1": 608, @@ -6275,6 +6976,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 609, + "mark": null, "x": 609, "y0": null, "y1": 609, @@ -6283,6 +6985,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 610, + "mark": null, "x": 610, "y0": null, "y1": 610, @@ -6291,6 +6994,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 611, + "mark": null, "x": 611, "y0": null, "y1": 611, @@ -6299,6 +7003,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 612, + "mark": null, "x": 612, "y0": null, "y1": 612, @@ -6307,6 +7012,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 613, + "mark": null, "x": 613, "y0": null, "y1": 613, @@ -6315,6 +7021,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 614, + "mark": null, "x": 614, "y0": null, "y1": 614, @@ -6323,6 +7030,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 615, + "mark": null, "x": 615, "y0": null, "y1": 615, @@ -6331,6 +7039,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 616, + "mark": null, "x": 616, "y0": null, "y1": 616, @@ -6339,6 +7048,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 617, + "mark": null, "x": 617, "y0": null, "y1": 617, @@ -6347,6 +7057,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 618, + "mark": null, "x": 618, "y0": null, "y1": 618, @@ -6355,6 +7066,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 619, + "mark": null, "x": 619, "y0": null, "y1": 619, @@ -6363,6 +7075,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 620, + "mark": null, "x": 620, "y0": null, "y1": 620, @@ -6371,6 +7084,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 621, + "mark": null, "x": 621, "y0": null, "y1": 621, @@ -6379,6 +7093,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 622, + "mark": null, "x": 622, "y0": null, "y1": 622, @@ -6387,6 +7102,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 623, + "mark": null, "x": 623, "y0": null, "y1": 623, @@ -6395,6 +7111,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 624, + "mark": null, "x": 624, "y0": null, "y1": 624, @@ -6403,6 +7120,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 625, + "mark": null, "x": 625, "y0": null, "y1": 625, @@ -6411,6 +7129,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 626, + "mark": null, "x": 626, "y0": null, "y1": 626, @@ -6419,6 +7138,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 627, + "mark": null, "x": 627, "y0": null, "y1": 627, @@ -6427,6 +7147,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 628, + "mark": null, "x": 628, "y0": null, "y1": 628, @@ -6435,6 +7156,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 629, + "mark": null, "x": 629, "y0": null, "y1": 629, @@ -6443,6 +7165,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 630, + "mark": null, "x": 630, "y0": null, "y1": 630, @@ -6451,6 +7174,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 631, + "mark": null, "x": 631, "y0": null, "y1": 631, @@ -6459,6 +7183,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 632, + "mark": null, "x": 632, "y0": null, "y1": 632, @@ -6467,6 +7192,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 633, + "mark": null, "x": 633, "y0": null, "y1": 633, @@ -6475,6 +7201,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 634, + "mark": null, "x": 634, "y0": null, "y1": 634, @@ -6483,6 +7210,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 635, + "mark": null, "x": 635, "y0": null, "y1": 635, @@ -6491,6 +7219,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 636, + "mark": null, "x": 636, "y0": null, "y1": 636, @@ -6499,6 +7228,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 637, + "mark": null, "x": 637, "y0": null, "y1": 637, @@ -6507,6 +7237,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 638, + "mark": null, "x": 638, "y0": null, "y1": 638, @@ -6515,6 +7246,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 639, + "mark": null, "x": 639, "y0": null, "y1": 639, @@ -6523,6 +7255,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 640, + "mark": null, "x": 640, "y0": null, "y1": 640, @@ -6531,6 +7264,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 641, + "mark": null, "x": 641, "y0": null, "y1": 641, @@ -6539,6 +7273,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 642, + "mark": null, "x": 642, "y0": null, "y1": 642, @@ -6547,6 +7282,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 643, + "mark": null, "x": 643, "y0": null, "y1": 643, @@ -6555,6 +7291,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 644, + "mark": null, "x": 644, "y0": null, "y1": 644, @@ -6563,6 +7300,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 645, + "mark": null, "x": 645, "y0": null, "y1": 645, @@ -6571,6 +7309,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 646, + "mark": null, "x": 646, "y0": null, "y1": 646, @@ -6579,6 +7318,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 647, + "mark": null, "x": 647, "y0": null, "y1": 647, @@ -6587,6 +7327,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 648, + "mark": null, "x": 648, "y0": null, "y1": 648, @@ -6595,6 +7336,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 649, + "mark": null, "x": 649, "y0": null, "y1": 649, @@ -6603,6 +7345,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 650, + "mark": null, "x": 650, "y0": null, "y1": 650, @@ -6611,6 +7354,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 651, + "mark": null, "x": 651, "y0": null, "y1": 651, @@ -6619,6 +7363,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 652, + "mark": null, "x": 652, "y0": null, "y1": 652, @@ -6627,6 +7372,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 653, + "mark": null, "x": 653, "y0": null, "y1": 653, @@ -6635,6 +7381,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 654, + "mark": null, "x": 654, "y0": null, "y1": 654, @@ -6643,6 +7390,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 655, + "mark": null, "x": 655, "y0": null, "y1": 655, @@ -6651,6 +7399,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 656, + "mark": null, "x": 656, "y0": null, "y1": 656, @@ -6659,6 +7408,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 657, + "mark": null, "x": 657, "y0": null, "y1": 657, @@ -6667,6 +7417,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 658, + "mark": null, "x": 658, "y0": null, "y1": 658, @@ -6675,6 +7426,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 659, + "mark": null, "x": 659, "y0": null, "y1": 659, @@ -6683,6 +7435,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 660, + "mark": null, "x": 660, "y0": null, "y1": 660, @@ -6691,6 +7444,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 661, + "mark": null, "x": 661, "y0": null, "y1": 661, @@ -6699,6 +7453,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 662, + "mark": null, "x": 662, "y0": null, "y1": 662, @@ -6707,6 +7462,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 663, + "mark": null, "x": 663, "y0": null, "y1": 663, @@ -6715,6 +7471,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 664, + "mark": null, "x": 664, "y0": null, "y1": 664, @@ -6723,6 +7480,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 665, + "mark": null, "x": 665, "y0": null, "y1": 665, @@ -6731,6 +7489,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 666, + "mark": null, "x": 666, "y0": null, "y1": 666, @@ -6739,6 +7498,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 667, + "mark": null, "x": 667, "y0": null, "y1": 667, @@ -6747,6 +7507,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 668, + "mark": null, "x": 668, "y0": null, "y1": 668, @@ -6755,6 +7516,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 669, + "mark": null, "x": 669, "y0": null, "y1": 669, @@ -6763,6 +7525,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 670, + "mark": null, "x": 670, "y0": null, "y1": 670, @@ -6771,6 +7534,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 671, + "mark": null, "x": 671, "y0": null, "y1": 671, @@ -6779,6 +7543,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 672, + "mark": null, "x": 672, "y0": null, "y1": 672, @@ -6787,6 +7552,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 673, + "mark": null, "x": 673, "y0": null, "y1": 673, @@ -6795,6 +7561,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 674, + "mark": null, "x": 674, "y0": null, "y1": 674, @@ -6803,6 +7570,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 675, + "mark": null, "x": 675, "y0": null, "y1": 675, @@ -6811,6 +7579,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 676, + "mark": null, "x": 676, "y0": null, "y1": 676, @@ -6819,6 +7588,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 677, + "mark": null, "x": 677, "y0": null, "y1": 677, @@ -6827,6 +7597,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 678, + "mark": null, "x": 678, "y0": null, "y1": 678, @@ -6835,6 +7606,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 679, + "mark": null, "x": 679, "y0": null, "y1": 679, @@ -6843,6 +7615,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 680, + "mark": null, "x": 680, "y0": null, "y1": 680, @@ -6851,6 +7624,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 681, + "mark": null, "x": 681, "y0": null, "y1": 681, @@ -6859,6 +7633,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 682, + "mark": null, "x": 682, "y0": null, "y1": 682, @@ -6867,6 +7642,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 683, + "mark": null, "x": 683, "y0": null, "y1": 683, @@ -6875,6 +7651,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 684, + "mark": null, "x": 684, "y0": null, "y1": 684, @@ -6883,6 +7660,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 685, + "mark": null, "x": 685, "y0": null, "y1": 685, @@ -6891,6 +7669,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 686, + "mark": null, "x": 686, "y0": null, "y1": 686, @@ -6899,6 +7678,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 687, + "mark": null, "x": 687, "y0": null, "y1": 687, @@ -6907,6 +7687,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 688, + "mark": null, "x": 688, "y0": null, "y1": 688, @@ -6915,6 +7696,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 689, + "mark": null, "x": 689, "y0": null, "y1": 689, @@ -6923,6 +7705,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 690, + "mark": null, "x": 690, "y0": null, "y1": 690, @@ -6931,6 +7714,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 691, + "mark": null, "x": 691, "y0": null, "y1": 691, @@ -6939,6 +7723,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 692, + "mark": null, "x": 692, "y0": null, "y1": 692, @@ -6947,6 +7732,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 693, + "mark": null, "x": 693, "y0": null, "y1": 693, @@ -6955,6 +7741,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 694, + "mark": null, "x": 694, "y0": null, "y1": 694, @@ -6963,6 +7750,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 695, + "mark": null, "x": 695, "y0": null, "y1": 695, @@ -6971,6 +7759,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 696, + "mark": null, "x": 696, "y0": null, "y1": 696, @@ -6979,6 +7768,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 697, + "mark": null, "x": 697, "y0": null, "y1": 697, @@ -6987,6 +7777,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 698, + "mark": null, "x": 698, "y0": null, "y1": 698, @@ -6995,6 +7786,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 699, + "mark": null, "x": 699, "y0": null, "y1": 699, @@ -7003,6 +7795,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 700, + "mark": null, "x": 700, "y0": null, "y1": 700, @@ -7011,6 +7804,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 701, + "mark": null, "x": 701, "y0": null, "y1": 701, @@ -7019,6 +7813,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 702, + "mark": null, "x": 702, "y0": null, "y1": 702, @@ -7027,6 +7822,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 703, + "mark": null, "x": 703, "y0": null, "y1": 703, @@ -7035,6 +7831,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 704, + "mark": null, "x": 704, "y0": null, "y1": 704, @@ -7043,6 +7840,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 705, + "mark": null, "x": 705, "y0": null, "y1": 705, @@ -7051,6 +7849,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 706, + "mark": null, "x": 706, "y0": null, "y1": 706, @@ -7059,6 +7858,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 707, + "mark": null, "x": 707, "y0": null, "y1": 707, @@ -7067,6 +7867,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 708, + "mark": null, "x": 708, "y0": null, "y1": 708, @@ -7075,6 +7876,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 709, + "mark": null, "x": 709, "y0": null, "y1": 709, @@ -7083,6 +7885,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 710, + "mark": null, "x": 710, "y0": null, "y1": 710, @@ -7091,6 +7894,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 711, + "mark": null, "x": 711, "y0": null, "y1": 711, @@ -7099,6 +7903,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 712, + "mark": null, "x": 712, "y0": null, "y1": 712, @@ -7107,6 +7912,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 713, + "mark": null, "x": 713, "y0": null, "y1": 713, @@ -7115,6 +7921,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 714, + "mark": null, "x": 714, "y0": null, "y1": 714, @@ -7123,6 +7930,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 715, + "mark": null, "x": 715, "y0": null, "y1": 715, @@ -7131,6 +7939,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 716, + "mark": null, "x": 716, "y0": null, "y1": 716, @@ -7139,6 +7948,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 717, + "mark": null, "x": 717, "y0": null, "y1": 717, @@ -7147,6 +7957,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 718, + "mark": null, "x": 718, "y0": null, "y1": 718, @@ -7155,6 +7966,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 719, + "mark": null, "x": 719, "y0": null, "y1": 719, @@ -7163,6 +7975,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 720, + "mark": null, "x": 720, "y0": null, "y1": 720, @@ -7171,6 +7984,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 721, + "mark": null, "x": 721, "y0": null, "y1": 721, @@ -7179,6 +7993,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 722, + "mark": null, "x": 722, "y0": null, "y1": 722, @@ -7187,6 +8002,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 723, + "mark": null, "x": 723, "y0": null, "y1": 723, @@ -7195,6 +8011,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 724, + "mark": null, "x": 724, "y0": null, "y1": 724, @@ -7203,6 +8020,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 725, + "mark": null, "x": 725, "y0": null, "y1": 725, @@ -7211,6 +8029,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 726, + "mark": null, "x": 726, "y0": null, "y1": 726, @@ -7219,6 +8038,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 727, + "mark": null, "x": 727, "y0": null, "y1": 727, @@ -7227,6 +8047,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 728, + "mark": null, "x": 728, "y0": null, "y1": 728, @@ -7235,6 +8056,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 729, + "mark": null, "x": 729, "y0": null, "y1": 729, @@ -7243,6 +8065,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 730, + "mark": null, "x": 730, "y0": null, "y1": 730, @@ -7251,6 +8074,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 731, + "mark": null, "x": 731, "y0": null, "y1": 731, @@ -7259,6 +8083,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 732, + "mark": null, "x": 732, "y0": null, "y1": 732, @@ -7267,6 +8092,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 733, + "mark": null, "x": 733, "y0": null, "y1": 733, @@ -7275,6 +8101,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 734, + "mark": null, "x": 734, "y0": null, "y1": 734, @@ -7283,6 +8110,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 735, + "mark": null, "x": 735, "y0": null, "y1": 735, @@ -7291,6 +8119,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 736, + "mark": null, "x": 736, "y0": null, "y1": 736, @@ -7299,6 +8128,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 737, + "mark": null, "x": 737, "y0": null, "y1": 737, @@ -7307,6 +8137,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 738, + "mark": null, "x": 738, "y0": null, "y1": 738, @@ -7315,6 +8146,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 739, + "mark": null, "x": 739, "y0": null, "y1": 739, @@ -7323,6 +8155,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 740, + "mark": null, "x": 740, "y0": null, "y1": 740, @@ -7331,6 +8164,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 741, + "mark": null, "x": 741, "y0": null, "y1": 741, @@ -7339,6 +8173,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 742, + "mark": null, "x": 742, "y0": null, "y1": 742, @@ -7347,6 +8182,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 743, + "mark": null, "x": 743, "y0": null, "y1": 743, @@ -7355,6 +8191,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 744, + "mark": null, "x": 744, "y0": null, "y1": 744, @@ -7363,6 +8200,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 745, + "mark": null, "x": 745, "y0": null, "y1": 745, @@ -7371,6 +8209,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 746, + "mark": null, "x": 746, "y0": null, "y1": 746, @@ -7379,6 +8218,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 747, + "mark": null, "x": 747, "y0": null, "y1": 747, @@ -7387,6 +8227,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 748, + "mark": null, "x": 748, "y0": null, "y1": 748, @@ -7395,6 +8236,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 749, + "mark": null, "x": 749, "y0": null, "y1": 749, @@ -7403,6 +8245,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 750, + "mark": null, "x": 750, "y0": null, "y1": 750, @@ -7411,6 +8254,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 751, + "mark": null, "x": 751, "y0": null, "y1": 751, @@ -7419,6 +8263,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 752, + "mark": null, "x": 752, "y0": null, "y1": 752, @@ -7427,6 +8272,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 753, + "mark": null, "x": 753, "y0": null, "y1": 753, @@ -7435,6 +8281,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 754, + "mark": null, "x": 754, "y0": null, "y1": 754, @@ -7443,6 +8290,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 755, + "mark": null, "x": 755, "y0": null, "y1": 755, @@ -7451,6 +8299,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 756, + "mark": null, "x": 756, "y0": null, "y1": 756, @@ -7459,6 +8308,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 757, + "mark": null, "x": 757, "y0": null, "y1": 757, @@ -7467,6 +8317,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 758, + "mark": null, "x": 758, "y0": null, "y1": 758, @@ -7475,6 +8326,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 759, + "mark": null, "x": 759, "y0": null, "y1": 759, @@ -7483,6 +8335,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 760, + "mark": null, "x": 760, "y0": null, "y1": 760, @@ -7491,6 +8344,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 761, + "mark": null, "x": 761, "y0": null, "y1": 761, @@ -7499,6 +8353,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 762, + "mark": null, "x": 762, "y0": null, "y1": 762, @@ -7507,6 +8362,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 763, + "mark": null, "x": 763, "y0": null, "y1": 763, @@ -7515,6 +8371,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 764, + "mark": null, "x": 764, "y0": null, "y1": 764, @@ -7523,6 +8380,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 765, + "mark": null, "x": 765, "y0": null, "y1": 765, @@ -7531,6 +8389,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 766, + "mark": null, "x": 766, "y0": null, "y1": 766, @@ -7539,6 +8398,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 767, + "mark": null, "x": 767, "y0": null, "y1": 767, @@ -7547,6 +8407,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 768, + "mark": null, "x": 768, "y0": null, "y1": 768, @@ -7555,6 +8416,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 769, + "mark": null, "x": 769, "y0": null, "y1": 769, @@ -7563,6 +8425,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 770, + "mark": null, "x": 770, "y0": null, "y1": 770, @@ -7571,6 +8434,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 771, + "mark": null, "x": 771, "y0": null, "y1": 771, @@ -7579,6 +8443,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 772, + "mark": null, "x": 772, "y0": null, "y1": 772, @@ -7587,6 +8452,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 773, + "mark": null, "x": 773, "y0": null, "y1": 773, @@ -7595,6 +8461,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 774, + "mark": null, "x": 774, "y0": null, "y1": 774, @@ -7603,6 +8470,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 775, + "mark": null, "x": 775, "y0": null, "y1": 775, @@ -7611,6 +8479,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 776, + "mark": null, "x": 776, "y0": null, "y1": 776, @@ -7619,6 +8488,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 777, + "mark": null, "x": 777, "y0": null, "y1": 777, @@ -7627,6 +8497,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 778, + "mark": null, "x": 778, "y0": null, "y1": 778, @@ -7635,6 +8506,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 779, + "mark": null, "x": 779, "y0": null, "y1": 779, @@ -7643,6 +8515,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 780, + "mark": null, "x": 780, "y0": null, "y1": 780, @@ -7651,6 +8524,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 781, + "mark": null, "x": 781, "y0": null, "y1": 781, @@ -7659,6 +8533,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 782, + "mark": null, "x": 782, "y0": null, "y1": 782, @@ -7667,6 +8542,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 783, + "mark": null, "x": 783, "y0": null, "y1": 783, @@ -7675,6 +8551,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 784, + "mark": null, "x": 784, "y0": null, "y1": 784, @@ -7683,6 +8560,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 785, + "mark": null, "x": 785, "y0": null, "y1": 785, @@ -7691,6 +8569,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 786, + "mark": null, "x": 786, "y0": null, "y1": 786, @@ -7699,6 +8578,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 787, + "mark": null, "x": 787, "y0": null, "y1": 787, @@ -7707,6 +8587,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 788, + "mark": null, "x": 788, "y0": null, "y1": 788, @@ -7715,6 +8596,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 789, + "mark": null, "x": 789, "y0": null, "y1": 789, @@ -7723,6 +8605,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 790, + "mark": null, "x": 790, "y0": null, "y1": 790, @@ -7731,6 +8614,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 791, + "mark": null, "x": 791, "y0": null, "y1": 791, @@ -7739,6 +8623,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 792, + "mark": null, "x": 792, "y0": null, "y1": 792, @@ -7747,6 +8632,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 793, + "mark": null, "x": 793, "y0": null, "y1": 793, @@ -7755,6 +8641,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 794, + "mark": null, "x": 794, "y0": null, "y1": 794, @@ -7763,6 +8650,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 795, + "mark": null, "x": 795, "y0": null, "y1": 795, @@ -7771,6 +8659,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 796, + "mark": null, "x": 796, "y0": null, "y1": 796, @@ -7779,6 +8668,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 797, + "mark": null, "x": 797, "y0": null, "y1": 797, @@ -7787,6 +8677,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 798, + "mark": null, "x": 798, "y0": null, "y1": 798, @@ -7795,6 +8686,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 799, + "mark": null, "x": 799, "y0": null, "y1": 799, @@ -7803,6 +8695,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 800, + "mark": null, "x": 800, "y0": null, "y1": 800, @@ -7811,6 +8704,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 801, + "mark": null, "x": 801, "y0": null, "y1": 801, @@ -7819,6 +8713,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 802, + "mark": null, "x": 802, "y0": null, "y1": 802, @@ -7827,6 +8722,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 803, + "mark": null, "x": 803, "y0": null, "y1": 803, @@ -7835,6 +8731,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 804, + "mark": null, "x": 804, "y0": null, "y1": 804, @@ -7843,6 +8740,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 805, + "mark": null, "x": 805, "y0": null, "y1": 805, @@ -7851,6 +8749,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 806, + "mark": null, "x": 806, "y0": null, "y1": 806, @@ -7859,6 +8758,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 807, + "mark": null, "x": 807, "y0": null, "y1": 807, @@ -7867,6 +8767,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 808, + "mark": null, "x": 808, "y0": null, "y1": 808, @@ -7875,6 +8776,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 809, + "mark": null, "x": 809, "y0": null, "y1": 809, @@ -7883,6 +8785,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 810, + "mark": null, "x": 810, "y0": null, "y1": 810, @@ -7891,6 +8794,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 811, + "mark": null, "x": 811, "y0": null, "y1": 811, @@ -7899,6 +8803,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 812, + "mark": null, "x": 812, "y0": null, "y1": 812, @@ -7907,6 +8812,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 813, + "mark": null, "x": 813, "y0": null, "y1": 813, @@ -7915,6 +8821,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 814, + "mark": null, "x": 814, "y0": null, "y1": 814, @@ -7923,6 +8830,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 815, + "mark": null, "x": 815, "y0": null, "y1": 815, @@ -7931,6 +8839,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 816, + "mark": null, "x": 816, "y0": null, "y1": 816, @@ -7939,6 +8848,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 817, + "mark": null, "x": 817, "y0": null, "y1": 817, @@ -7947,6 +8857,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 818, + "mark": null, "x": 818, "y0": null, "y1": 818, @@ -7955,6 +8866,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 819, + "mark": null, "x": 819, "y0": null, "y1": 819, @@ -7963,6 +8875,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 820, + "mark": null, "x": 820, "y0": null, "y1": 820, @@ -7971,6 +8884,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 821, + "mark": null, "x": 821, "y0": null, "y1": 821, @@ -7979,6 +8893,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 822, + "mark": null, "x": 822, "y0": null, "y1": 822, @@ -7987,6 +8902,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 823, + "mark": null, "x": 823, "y0": null, "y1": 823, @@ -7995,6 +8911,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 824, + "mark": null, "x": 824, "y0": null, "y1": 824, @@ -8003,6 +8920,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 825, + "mark": null, "x": 825, "y0": null, "y1": 825, @@ -8011,6 +8929,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 826, + "mark": null, "x": 826, "y0": null, "y1": 826, @@ -8019,6 +8938,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 827, + "mark": null, "x": 827, "y0": null, "y1": 827, @@ -8027,6 +8947,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 828, + "mark": null, "x": 828, "y0": null, "y1": 828, @@ -8035,6 +8956,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 829, + "mark": null, "x": 829, "y0": null, "y1": 829, @@ -8043,6 +8965,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 830, + "mark": null, "x": 830, "y0": null, "y1": 830, @@ -8051,6 +8974,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 831, + "mark": null, "x": 831, "y0": null, "y1": 831, @@ -8059,6 +8983,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 832, + "mark": null, "x": 832, "y0": null, "y1": 832, @@ -8067,6 +8992,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 833, + "mark": null, "x": 833, "y0": null, "y1": 833, @@ -8075,6 +9001,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 834, + "mark": null, "x": 834, "y0": null, "y1": 834, @@ -8083,6 +9010,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 835, + "mark": null, "x": 835, "y0": null, "y1": 835, @@ -8091,6 +9019,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 836, + "mark": null, "x": 836, "y0": null, "y1": 836, @@ -8099,6 +9028,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 837, + "mark": null, "x": 837, "y0": null, "y1": 837, @@ -8107,6 +9037,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 838, + "mark": null, "x": 838, "y0": null, "y1": 838, @@ -8115,6 +9046,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 839, + "mark": null, "x": 839, "y0": null, "y1": 839, @@ -8123,6 +9055,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 840, + "mark": null, "x": 840, "y0": null, "y1": 840, @@ -8131,6 +9064,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 841, + "mark": null, "x": 841, "y0": null, "y1": 841, @@ -8139,6 +9073,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 842, + "mark": null, "x": 842, "y0": null, "y1": 842, @@ -8147,6 +9082,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 843, + "mark": null, "x": 843, "y0": null, "y1": 843, @@ -8155,6 +9091,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 844, + "mark": null, "x": 844, "y0": null, "y1": 844, @@ -8163,6 +9100,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 845, + "mark": null, "x": 845, "y0": null, "y1": 845, @@ -8171,6 +9109,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 846, + "mark": null, "x": 846, "y0": null, "y1": 846, @@ -8179,6 +9118,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 847, + "mark": null, "x": 847, "y0": null, "y1": 847, @@ -8187,6 +9127,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 848, + "mark": null, "x": 848, "y0": null, "y1": 848, @@ -8195,6 +9136,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 849, + "mark": null, "x": 849, "y0": null, "y1": 849, @@ -8203,6 +9145,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 850, + "mark": null, "x": 850, "y0": null, "y1": 850, @@ -8211,6 +9154,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 851, + "mark": null, "x": 851, "y0": null, "y1": 851, @@ -8219,6 +9163,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 852, + "mark": null, "x": 852, "y0": null, "y1": 852, @@ -8227,6 +9172,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 853, + "mark": null, "x": 853, "y0": null, "y1": 853, @@ -8235,6 +9181,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 854, + "mark": null, "x": 854, "y0": null, "y1": 854, @@ -8243,6 +9190,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 855, + "mark": null, "x": 855, "y0": null, "y1": 855, @@ -8251,6 +9199,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 856, + "mark": null, "x": 856, "y0": null, "y1": 856, @@ -8259,6 +9208,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 857, + "mark": null, "x": 857, "y0": null, "y1": 857, @@ -8267,6 +9217,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 858, + "mark": null, "x": 858, "y0": null, "y1": 858, @@ -8275,6 +9226,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 859, + "mark": null, "x": 859, "y0": null, "y1": 859, @@ -8283,6 +9235,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 860, + "mark": null, "x": 860, "y0": null, "y1": 860, @@ -8291,6 +9244,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 861, + "mark": null, "x": 861, "y0": null, "y1": 861, @@ -8299,6 +9253,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 862, + "mark": null, "x": 862, "y0": null, "y1": 862, @@ -8307,6 +9262,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 863, + "mark": null, "x": 863, "y0": null, "y1": 863, @@ -8315,6 +9271,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 864, + "mark": null, "x": 864, "y0": null, "y1": 864, @@ -8323,6 +9280,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 865, + "mark": null, "x": 865, "y0": null, "y1": 865, @@ -8331,6 +9289,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 866, + "mark": null, "x": 866, "y0": null, "y1": 866, @@ -8339,6 +9298,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 867, + "mark": null, "x": 867, "y0": null, "y1": 867, @@ -8347,6 +9307,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 868, + "mark": null, "x": 868, "y0": null, "y1": 868, @@ -8355,6 +9316,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 869, + "mark": null, "x": 869, "y0": null, "y1": 869, @@ -8363,6 +9325,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 870, + "mark": null, "x": 870, "y0": null, "y1": 870, @@ -8371,6 +9334,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 871, + "mark": null, "x": 871, "y0": null, "y1": 871, @@ -8379,6 +9343,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 872, + "mark": null, "x": 872, "y0": null, "y1": 872, @@ -8387,6 +9352,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 873, + "mark": null, "x": 873, "y0": null, "y1": 873, @@ -8395,6 +9361,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 874, + "mark": null, "x": 874, "y0": null, "y1": 874, @@ -8403,6 +9370,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 875, + "mark": null, "x": 875, "y0": null, "y1": 875, @@ -8411,6 +9379,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 876, + "mark": null, "x": 876, "y0": null, "y1": 876, @@ -8419,6 +9388,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 877, + "mark": null, "x": 877, "y0": null, "y1": 877, @@ -8427,6 +9397,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 878, + "mark": null, "x": 878, "y0": null, "y1": 878, @@ -8435,6 +9406,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 879, + "mark": null, "x": 879, "y0": null, "y1": 879, @@ -8443,6 +9415,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 880, + "mark": null, "x": 880, "y0": null, "y1": 880, @@ -8451,6 +9424,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 881, + "mark": null, "x": 881, "y0": null, "y1": 881, @@ -8459,6 +9433,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 882, + "mark": null, "x": 882, "y0": null, "y1": 882, @@ -8467,6 +9442,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 883, + "mark": null, "x": 883, "y0": null, "y1": 883, @@ -8475,6 +9451,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 884, + "mark": null, "x": 884, "y0": null, "y1": 884, @@ -8483,6 +9460,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 885, + "mark": null, "x": 885, "y0": null, "y1": 885, @@ -8491,6 +9469,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 886, + "mark": null, "x": 886, "y0": null, "y1": 886, @@ -8499,6 +9478,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 887, + "mark": null, "x": 887, "y0": null, "y1": 887, @@ -8507,6 +9487,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 888, + "mark": null, "x": 888, "y0": null, "y1": 888, @@ -8515,6 +9496,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 889, + "mark": null, "x": 889, "y0": null, "y1": 889, @@ -8523,6 +9505,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 890, + "mark": null, "x": 890, "y0": null, "y1": 890, @@ -8531,6 +9514,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 891, + "mark": null, "x": 891, "y0": null, "y1": 891, @@ -8539,6 +9523,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 892, + "mark": null, "x": 892, "y0": null, "y1": 892, @@ -8547,6 +9532,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 893, + "mark": null, "x": 893, "y0": null, "y1": 893, @@ -8555,6 +9541,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 894, + "mark": null, "x": 894, "y0": null, "y1": 894, @@ -8563,6 +9550,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 895, + "mark": null, "x": 895, "y0": null, "y1": 895, @@ -8571,6 +9559,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 896, + "mark": null, "x": 896, "y0": null, "y1": 896, @@ -8579,6 +9568,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 897, + "mark": null, "x": 897, "y0": null, "y1": 897, @@ -8587,6 +9577,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 898, + "mark": null, "x": 898, "y0": null, "y1": 898, @@ -8595,6 +9586,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 899, + "mark": null, "x": 899, "y0": null, "y1": 899, @@ -8603,6 +9595,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 900, + "mark": null, "x": 900, "y0": null, "y1": 900, @@ -8611,6 +9604,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 901, + "mark": null, "x": 901, "y0": null, "y1": 901, @@ -8619,6 +9613,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 902, + "mark": null, "x": 902, "y0": null, "y1": 902, @@ -8627,6 +9622,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 903, + "mark": null, "x": 903, "y0": null, "y1": 903, @@ -8635,6 +9631,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 904, + "mark": null, "x": 904, "y0": null, "y1": 904, @@ -8643,6 +9640,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 905, + "mark": null, "x": 905, "y0": null, "y1": 905, @@ -8651,6 +9649,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 906, + "mark": null, "x": 906, "y0": null, "y1": 906, @@ -8659,6 +9658,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 907, + "mark": null, "x": 907, "y0": null, "y1": 907, @@ -8667,6 +9667,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 908, + "mark": null, "x": 908, "y0": null, "y1": 908, @@ -8675,6 +9676,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 909, + "mark": null, "x": 909, "y0": null, "y1": 909, @@ -8683,6 +9685,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 910, + "mark": null, "x": 910, "y0": null, "y1": 910, @@ -8691,6 +9694,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 911, + "mark": null, "x": 911, "y0": null, "y1": 911, @@ -8699,6 +9703,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 912, + "mark": null, "x": 912, "y0": null, "y1": 912, @@ -8707,6 +9712,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 913, + "mark": null, "x": 913, "y0": null, "y1": 913, @@ -8715,6 +9721,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 914, + "mark": null, "x": 914, "y0": null, "y1": 914, @@ -8723,6 +9730,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 915, + "mark": null, "x": 915, "y0": null, "y1": 915, @@ -8731,6 +9739,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 916, + "mark": null, "x": 916, "y0": null, "y1": 916, @@ -8739,6 +9748,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 917, + "mark": null, "x": 917, "y0": null, "y1": 917, @@ -8747,6 +9757,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 918, + "mark": null, "x": 918, "y0": null, "y1": 918, @@ -8755,6 +9766,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 919, + "mark": null, "x": 919, "y0": null, "y1": 919, @@ -8763,6 +9775,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 920, + "mark": null, "x": 920, "y0": null, "y1": 920, @@ -8771,6 +9784,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 921, + "mark": null, "x": 921, "y0": null, "y1": 921, @@ -8779,6 +9793,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 922, + "mark": null, "x": 922, "y0": null, "y1": 922, @@ -8787,6 +9802,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 923, + "mark": null, "x": 923, "y0": null, "y1": 923, @@ -8795,6 +9811,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 924, + "mark": null, "x": 924, "y0": null, "y1": 924, @@ -8803,6 +9820,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 925, + "mark": null, "x": 925, "y0": null, "y1": 925, @@ -8811,6 +9829,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 926, + "mark": null, "x": 926, "y0": null, "y1": 926, @@ -8819,6 +9838,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 927, + "mark": null, "x": 927, "y0": null, "y1": 927, @@ -8827,6 +9847,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 928, + "mark": null, "x": 928, "y0": null, "y1": 928, @@ -8835,6 +9856,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 929, + "mark": null, "x": 929, "y0": null, "y1": 929, @@ -8843,6 +9865,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 930, + "mark": null, "x": 930, "y0": null, "y1": 930, @@ -8851,6 +9874,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 931, + "mark": null, "x": 931, "y0": null, "y1": 931, @@ -8859,6 +9883,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 932, + "mark": null, "x": 932, "y0": null, "y1": 932, @@ -8867,6 +9892,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 933, + "mark": null, "x": 933, "y0": null, "y1": 933, @@ -8875,6 +9901,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 934, + "mark": null, "x": 934, "y0": null, "y1": 934, @@ -8883,6 +9910,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 935, + "mark": null, "x": 935, "y0": null, "y1": 935, @@ -8891,6 +9919,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 936, + "mark": null, "x": 936, "y0": null, "y1": 936, @@ -8899,6 +9928,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 937, + "mark": null, "x": 937, "y0": null, "y1": 937, @@ -8907,6 +9937,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 938, + "mark": null, "x": 938, "y0": null, "y1": 938, @@ -8915,6 +9946,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 939, + "mark": null, "x": 939, "y0": null, "y1": 939, @@ -8923,6 +9955,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 940, + "mark": null, "x": 940, "y0": null, "y1": 940, @@ -8931,6 +9964,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 941, + "mark": null, "x": 941, "y0": null, "y1": 941, @@ -8939,6 +9973,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 942, + "mark": null, "x": 942, "y0": null, "y1": 942, @@ -8947,6 +9982,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 943, + "mark": null, "x": 943, "y0": null, "y1": 943, @@ -8955,6 +9991,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 944, + "mark": null, "x": 944, "y0": null, "y1": 944, @@ -8963,6 +10000,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 945, + "mark": null, "x": 945, "y0": null, "y1": 945, @@ -8971,6 +10009,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 946, + "mark": null, "x": 946, "y0": null, "y1": 946, @@ -8979,6 +10018,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 947, + "mark": null, "x": 947, "y0": null, "y1": 947, @@ -8987,6 +10027,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 948, + "mark": null, "x": 948, "y0": null, "y1": 948, @@ -8995,6 +10036,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 949, + "mark": null, "x": 949, "y0": null, "y1": 949, @@ -9003,6 +10045,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 950, + "mark": null, "x": 950, "y0": null, "y1": 950, @@ -9011,6 +10054,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 951, + "mark": null, "x": 951, "y0": null, "y1": 951, @@ -9019,6 +10063,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 952, + "mark": null, "x": 952, "y0": null, "y1": 952, @@ -9027,6 +10072,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 953, + "mark": null, "x": 953, "y0": null, "y1": 953, @@ -9035,6 +10081,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 954, + "mark": null, "x": 954, "y0": null, "y1": 954, @@ -9043,6 +10090,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 955, + "mark": null, "x": 955, "y0": null, "y1": 955, @@ -9051,6 +10099,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 956, + "mark": null, "x": 956, "y0": null, "y1": 956, @@ -9059,6 +10108,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 957, + "mark": null, "x": 957, "y0": null, "y1": 957, @@ -9067,6 +10117,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 958, + "mark": null, "x": 958, "y0": null, "y1": 958, @@ -9075,6 +10126,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 959, + "mark": null, "x": 959, "y0": null, "y1": 959, @@ -9083,6 +10135,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 960, + "mark": null, "x": 960, "y0": null, "y1": 960, @@ -9091,6 +10144,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 961, + "mark": null, "x": 961, "y0": null, "y1": 961, @@ -9099,6 +10153,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 962, + "mark": null, "x": 962, "y0": null, "y1": 962, @@ -9107,6 +10162,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 963, + "mark": null, "x": 963, "y0": null, "y1": 963, @@ -9115,6 +10171,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 964, + "mark": null, "x": 964, "y0": null, "y1": 964, @@ -9123,6 +10180,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 965, + "mark": null, "x": 965, "y0": null, "y1": 965, @@ -9131,6 +10189,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 966, + "mark": null, "x": 966, "y0": null, "y1": 966, @@ -9139,6 +10198,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 967, + "mark": null, "x": 967, "y0": null, "y1": 967, @@ -9147,6 +10207,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 968, + "mark": null, "x": 968, "y0": null, "y1": 968, @@ -9155,6 +10216,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 969, + "mark": null, "x": 969, "y0": null, "y1": 969, @@ -9163,6 +10225,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 970, + "mark": null, "x": 970, "y0": null, "y1": 970, @@ -9171,6 +10234,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 971, + "mark": null, "x": 971, "y0": null, "y1": 971, @@ -9179,6 +10243,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 972, + "mark": null, "x": 972, "y0": null, "y1": 972, @@ -9187,6 +10252,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 973, + "mark": null, "x": 973, "y0": null, "y1": 973, @@ -9195,6 +10261,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 974, + "mark": null, "x": 974, "y0": null, "y1": 974, @@ -9203,6 +10270,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 975, + "mark": null, "x": 975, "y0": null, "y1": 975, @@ -9211,6 +10279,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 976, + "mark": null, "x": 976, "y0": null, "y1": 976, @@ -9219,6 +10288,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 977, + "mark": null, "x": 977, "y0": null, "y1": 977, @@ -9227,6 +10297,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 978, + "mark": null, "x": 978, "y0": null, "y1": 978, @@ -9235,6 +10306,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 979, + "mark": null, "x": 979, "y0": null, "y1": 979, @@ -9243,6 +10315,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 980, + "mark": null, "x": 980, "y0": null, "y1": 980, @@ -9251,6 +10324,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 981, + "mark": null, "x": 981, "y0": null, "y1": 981, @@ -9259,6 +10333,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 982, + "mark": null, "x": 982, "y0": null, "y1": 982, @@ -9267,6 +10342,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 983, + "mark": null, "x": 983, "y0": null, "y1": 983, @@ -9275,6 +10351,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 984, + "mark": null, "x": 984, "y0": null, "y1": 984, @@ -9283,6 +10360,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 985, + "mark": null, "x": 985, "y0": null, "y1": 985, @@ -9291,6 +10369,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 986, + "mark": null, "x": 986, "y0": null, "y1": 986, @@ -9299,6 +10378,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 987, + "mark": null, "x": 987, "y0": null, "y1": 987, @@ -9307,6 +10387,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 988, + "mark": null, "x": 988, "y0": null, "y1": 988, @@ -9315,6 +10396,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 989, + "mark": null, "x": 989, "y0": null, "y1": 989, @@ -9323,6 +10405,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 990, + "mark": null, "x": 990, "y0": null, "y1": 990, @@ -9331,6 +10414,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 991, + "mark": null, "x": 991, "y0": null, "y1": 991, @@ -9339,6 +10423,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 992, + "mark": null, "x": 992, "y0": null, "y1": 992, @@ -9347,6 +10432,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 993, + "mark": null, "x": 993, "y0": null, "y1": 993, @@ -9355,6 +10441,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 994, + "mark": null, "x": 994, "y0": null, "y1": 994, @@ -9363,6 +10450,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 995, + "mark": null, "x": 995, "y0": null, "y1": 995, @@ -9371,6 +10459,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 996, + "mark": null, "x": 996, "y0": null, "y1": 996, @@ -9379,6 +10468,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 997, + "mark": null, "x": 997, "y0": null, "y1": 997, @@ -9387,6 +10477,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 998, + "mark": null, "x": 998, "y0": null, "y1": 998, @@ -9395,6 +10486,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 999, + "mark": null, "x": 999, "y0": null, "y1": 999, @@ -9414,6 +10506,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 0, + "mark": null, "x": 0, "y0": 0, "y1": 0, @@ -9422,6 +10515,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": 1, "y1": 2, @@ -9430,6 +10524,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": 2, "y1": 4, @@ -9438,6 +10533,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 3, + "mark": null, "x": 3, "y0": 3, "y1": 6, @@ -9446,6 +10542,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": 4, "y1": 8, @@ -9454,6 +10551,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 5, + "mark": null, "x": 5, "y0": 5, "y1": 10, @@ -9462,6 +10560,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 6, + "mark": null, "x": 6, "y0": 6, "y1": 12, @@ -9470,6 +10569,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 7, + "mark": null, "x": 7, "y0": 7, "y1": 14, @@ -9478,6 +10578,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 8, + "mark": null, "x": 8, "y0": 8, "y1": 16, @@ -9486,6 +10587,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 9, + "mark": null, "x": 9, "y0": 9, "y1": 18, @@ -9494,6 +10596,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 10, + "mark": null, "x": 10, "y0": 10, "y1": 20, @@ -9502,6 +10605,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 11, + "mark": null, "x": 11, "y0": 11, "y1": 22, @@ -9510,6 +10614,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 12, + "mark": null, "x": 12, "y0": 12, "y1": 24, @@ -9518,6 +10623,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 13, + "mark": null, "x": 13, "y0": 13, "y1": 26, @@ -9526,6 +10632,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 14, + "mark": null, "x": 14, "y0": 14, "y1": 28, @@ -9534,6 +10641,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 15, + "mark": null, "x": 15, "y0": 15, "y1": 30, @@ -9542,6 +10650,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 16, + "mark": null, "x": 16, "y0": 16, "y1": 32, @@ -9550,6 +10659,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 17, + "mark": null, "x": 17, "y0": 17, "y1": 34, @@ -9558,6 +10668,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 18, + "mark": null, "x": 18, "y0": 18, "y1": 36, @@ -9566,6 +10677,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 19, + "mark": null, "x": 19, "y0": 19, "y1": 38, @@ -9574,6 +10686,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 20, + "mark": null, "x": 20, "y0": 20, "y1": 40, @@ -9582,6 +10695,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 21, + "mark": null, "x": 21, "y0": 21, "y1": 42, @@ -9590,6 +10704,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 22, + "mark": null, "x": 22, "y0": 22, "y1": 44, @@ -9598,6 +10713,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 23, + "mark": null, "x": 23, "y0": 23, "y1": 46, @@ -9606,6 +10722,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 24, + "mark": null, "x": 24, "y0": 24, "y1": 48, @@ -9614,6 +10731,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 25, + "mark": null, "x": 25, "y0": 25, "y1": 50, @@ -9622,6 +10740,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 26, + "mark": null, "x": 26, "y0": 26, "y1": 52, @@ -9630,6 +10749,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 27, + "mark": null, "x": 27, "y0": 27, "y1": 54, @@ -9638,6 +10758,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 28, + "mark": null, "x": 28, "y0": 28, "y1": 56, @@ -9646,6 +10767,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 29, + "mark": null, "x": 29, "y0": 29, "y1": 58, @@ -9654,6 +10776,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 30, + "mark": null, "x": 30, "y0": 30, "y1": 60, @@ -9662,6 +10785,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 31, + "mark": null, "x": 31, "y0": 31, "y1": 62, @@ -9670,6 +10794,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 32, + "mark": null, "x": 32, "y0": 32, "y1": 64, @@ -9678,6 +10803,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 33, + "mark": null, "x": 33, "y0": 33, "y1": 66, @@ -9686,6 +10812,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 34, + "mark": null, "x": 34, "y0": 34, "y1": 68, @@ -9694,6 +10821,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 35, + "mark": null, "x": 35, "y0": 35, "y1": 70, @@ -9702,6 +10830,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 36, + "mark": null, "x": 36, "y0": 36, "y1": 72, @@ -9710,6 +10839,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 37, + "mark": null, "x": 37, "y0": 37, "y1": 74, @@ -9718,6 +10848,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 38, + "mark": null, "x": 38, "y0": 38, "y1": 76, @@ -9726,6 +10857,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 39, + "mark": null, "x": 39, "y0": 39, "y1": 78, @@ -9734,6 +10866,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 40, + "mark": null, "x": 40, "y0": 40, "y1": 80, @@ -9742,6 +10875,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 41, + "mark": null, "x": 41, "y0": 41, "y1": 82, @@ -9750,6 +10884,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 42, + "mark": null, "x": 42, "y0": 42, "y1": 84, @@ -9758,6 +10893,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 43, + "mark": null, "x": 43, "y0": 43, "y1": 86, @@ -9766,6 +10902,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 44, + "mark": null, "x": 44, "y0": 44, "y1": 88, @@ -9774,6 +10911,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 45, + "mark": null, "x": 45, "y0": 45, "y1": 90, @@ -9782,6 +10920,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 46, + "mark": null, "x": 46, "y0": 46, "y1": 92, @@ -9790,6 +10929,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 47, + "mark": null, "x": 47, "y0": 47, "y1": 94, @@ -9798,6 +10938,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 48, + "mark": null, "x": 48, "y0": 48, "y1": 96, @@ -9806,6 +10947,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 49, + "mark": null, "x": 49, "y0": 49, "y1": 98, @@ -9814,6 +10956,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 50, + "mark": null, "x": 50, "y0": 50, "y1": 100, @@ -9822,6 +10965,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 51, + "mark": null, "x": 51, "y0": 51, "y1": 102, @@ -9830,6 +10974,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 52, + "mark": null, "x": 52, "y0": 52, "y1": 104, @@ -9838,6 +10983,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 53, + "mark": null, "x": 53, "y0": 53, "y1": 106, @@ -9846,6 +10992,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 54, + "mark": null, "x": 54, "y0": 54, "y1": 108, @@ -9854,6 +11001,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 55, + "mark": null, "x": 55, "y0": 55, "y1": 110, @@ -9862,6 +11010,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 56, + "mark": null, "x": 56, "y0": 56, "y1": 112, @@ -9870,6 +11019,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 57, + "mark": null, "x": 57, "y0": 57, "y1": 114, @@ -9878,6 +11028,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 58, + "mark": null, "x": 58, "y0": 58, "y1": 116, @@ -9886,6 +11037,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 59, + "mark": null, "x": 59, "y0": 59, "y1": 118, @@ -9894,6 +11046,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 60, + "mark": null, "x": 60, "y0": 60, "y1": 120, @@ -9902,6 +11055,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 61, + "mark": null, "x": 61, "y0": 61, "y1": 122, @@ -9910,6 +11064,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 62, + "mark": null, "x": 62, "y0": 62, "y1": 124, @@ -9918,6 +11073,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 63, + "mark": null, "x": 63, "y0": 63, "y1": 126, @@ -9926,6 +11082,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 64, + "mark": null, "x": 64, "y0": 64, "y1": 128, @@ -9934,6 +11091,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 65, + "mark": null, "x": 65, "y0": 65, "y1": 130, @@ -9942,6 +11100,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 66, + "mark": null, "x": 66, "y0": 66, "y1": 132, @@ -9950,6 +11109,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 67, + "mark": null, "x": 67, "y0": 67, "y1": 134, @@ -9958,6 +11118,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 68, + "mark": null, "x": 68, "y0": 68, "y1": 136, @@ -9966,6 +11127,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 69, + "mark": null, "x": 69, "y0": 69, "y1": 138, @@ -9974,6 +11136,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 70, + "mark": null, "x": 70, "y0": 70, "y1": 140, @@ -9982,6 +11145,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 71, + "mark": null, "x": 71, "y0": 71, "y1": 142, @@ -9990,6 +11154,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 72, + "mark": null, "x": 72, "y0": 72, "y1": 144, @@ -9998,6 +11163,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 73, + "mark": null, "x": 73, "y0": 73, "y1": 146, @@ -10006,6 +11172,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 74, + "mark": null, "x": 74, "y0": 74, "y1": 148, @@ -10014,6 +11181,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 75, + "mark": null, "x": 75, "y0": 75, "y1": 150, @@ -10022,6 +11190,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 76, + "mark": null, "x": 76, "y0": 76, "y1": 152, @@ -10030,6 +11199,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 77, + "mark": null, "x": 77, "y0": 77, "y1": 154, @@ -10038,6 +11208,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 78, + "mark": null, "x": 78, "y0": 78, "y1": 156, @@ -10046,6 +11217,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 79, + "mark": null, "x": 79, "y0": 79, "y1": 158, @@ -10054,6 +11226,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 80, + "mark": null, "x": 80, "y0": 80, "y1": 160, @@ -10062,6 +11235,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 81, + "mark": null, "x": 81, "y0": 81, "y1": 162, @@ -10070,6 +11244,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 82, + "mark": null, "x": 82, "y0": 82, "y1": 164, @@ -10078,6 +11253,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 83, + "mark": null, "x": 83, "y0": 83, "y1": 166, @@ -10086,6 +11262,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 84, + "mark": null, "x": 84, "y0": 84, "y1": 168, @@ -10094,6 +11271,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 85, + "mark": null, "x": 85, "y0": 85, "y1": 170, @@ -10102,6 +11280,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 86, + "mark": null, "x": 86, "y0": 86, "y1": 172, @@ -10110,6 +11289,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 87, + "mark": null, "x": 87, "y0": 87, "y1": 174, @@ -10118,6 +11298,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 88, + "mark": null, "x": 88, "y0": 88, "y1": 176, @@ -10126,6 +11307,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 89, + "mark": null, "x": 89, "y0": 89, "y1": 178, @@ -10134,6 +11316,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 90, + "mark": null, "x": 90, "y0": 90, "y1": 180, @@ -10142,6 +11325,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 91, + "mark": null, "x": 91, "y0": 91, "y1": 182, @@ -10150,6 +11334,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 92, + "mark": null, "x": 92, "y0": 92, "y1": 184, @@ -10158,6 +11343,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 93, + "mark": null, "x": 93, "y0": 93, "y1": 186, @@ -10166,6 +11352,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 94, + "mark": null, "x": 94, "y0": 94, "y1": 188, @@ -10174,6 +11361,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 95, + "mark": null, "x": 95, "y0": 95, "y1": 190, @@ -10182,6 +11370,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 96, + "mark": null, "x": 96, "y0": 96, "y1": 192, @@ -10190,6 +11379,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 97, + "mark": null, "x": 97, "y0": 97, "y1": 194, @@ -10198,6 +11388,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 98, + "mark": null, "x": 98, "y0": 98, "y1": 196, @@ -10206,6 +11397,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 99, + "mark": null, "x": 99, "y0": 99, "y1": 198, @@ -10214,6 +11406,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 100, + "mark": null, "x": 100, "y0": 100, "y1": 200, @@ -10222,6 +11415,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 101, + "mark": null, "x": 101, "y0": 101, "y1": 202, @@ -10230,6 +11424,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 102, + "mark": null, "x": 102, "y0": 102, "y1": 204, @@ -10238,6 +11433,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 103, + "mark": null, "x": 103, "y0": 103, "y1": 206, @@ -10246,6 +11442,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 104, + "mark": null, "x": 104, "y0": 104, "y1": 208, @@ -10254,6 +11451,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 105, + "mark": null, "x": 105, "y0": 105, "y1": 210, @@ -10262,6 +11460,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 106, + "mark": null, "x": 106, "y0": 106, "y1": 212, @@ -10270,6 +11469,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 107, + "mark": null, "x": 107, "y0": 107, "y1": 214, @@ -10278,6 +11478,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 108, + "mark": null, "x": 108, "y0": 108, "y1": 216, @@ -10286,6 +11487,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 109, + "mark": null, "x": 109, "y0": 109, "y1": 218, @@ -10294,6 +11496,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 110, + "mark": null, "x": 110, "y0": 110, "y1": 220, @@ -10302,6 +11505,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 111, + "mark": null, "x": 111, "y0": 111, "y1": 222, @@ -10310,6 +11514,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 112, + "mark": null, "x": 112, "y0": 112, "y1": 224, @@ -10318,6 +11523,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 113, + "mark": null, "x": 113, "y0": 113, "y1": 226, @@ -10326,6 +11532,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 114, + "mark": null, "x": 114, "y0": 114, "y1": 228, @@ -10334,6 +11541,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 115, + "mark": null, "x": 115, "y0": 115, "y1": 230, @@ -10342,6 +11550,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 116, + "mark": null, "x": 116, "y0": 116, "y1": 232, @@ -10350,6 +11559,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 117, + "mark": null, "x": 117, "y0": 117, "y1": 234, @@ -10358,6 +11568,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 118, + "mark": null, "x": 118, "y0": 118, "y1": 236, @@ -10366,6 +11577,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 119, + "mark": null, "x": 119, "y0": 119, "y1": 238, @@ -10374,6 +11586,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 120, + "mark": null, "x": 120, "y0": 120, "y1": 240, @@ -10382,6 +11595,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 121, + "mark": null, "x": 121, "y0": 121, "y1": 242, @@ -10390,6 +11604,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 122, + "mark": null, "x": 122, "y0": 122, "y1": 244, @@ -10398,6 +11613,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 123, + "mark": null, "x": 123, "y0": 123, "y1": 246, @@ -10406,6 +11622,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 124, + "mark": null, "x": 124, "y0": 124, "y1": 248, @@ -10414,6 +11631,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 125, + "mark": null, "x": 125, "y0": 125, "y1": 250, @@ -10422,6 +11640,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 126, + "mark": null, "x": 126, "y0": 126, "y1": 252, @@ -10430,6 +11649,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 127, + "mark": null, "x": 127, "y0": 127, "y1": 254, @@ -10438,6 +11658,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 128, + "mark": null, "x": 128, "y0": 128, "y1": 256, @@ -10446,6 +11667,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 129, + "mark": null, "x": 129, "y0": 129, "y1": 258, @@ -10454,6 +11676,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 130, + "mark": null, "x": 130, "y0": 130, "y1": 260, @@ -10462,6 +11685,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 131, + "mark": null, "x": 131, "y0": 131, "y1": 262, @@ -10470,6 +11694,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 132, + "mark": null, "x": 132, "y0": 132, "y1": 264, @@ -10478,6 +11703,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 133, + "mark": null, "x": 133, "y0": 133, "y1": 266, @@ -10486,6 +11712,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 134, + "mark": null, "x": 134, "y0": 134, "y1": 268, @@ -10494,6 +11721,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 135, + "mark": null, "x": 135, "y0": 135, "y1": 270, @@ -10502,6 +11730,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 136, + "mark": null, "x": 136, "y0": 136, "y1": 272, @@ -10510,6 +11739,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 137, + "mark": null, "x": 137, "y0": 137, "y1": 274, @@ -10518,6 +11748,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 138, + "mark": null, "x": 138, "y0": 138, "y1": 276, @@ -10526,6 +11757,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 139, + "mark": null, "x": 139, "y0": 139, "y1": 278, @@ -10534,6 +11766,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 140, + "mark": null, "x": 140, "y0": 140, "y1": 280, @@ -10542,6 +11775,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 141, + "mark": null, "x": 141, "y0": 141, "y1": 282, @@ -10550,6 +11784,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 142, + "mark": null, "x": 142, "y0": 142, "y1": 284, @@ -10558,6 +11793,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 143, + "mark": null, "x": 143, "y0": 143, "y1": 286, @@ -10566,6 +11802,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 144, + "mark": null, "x": 144, "y0": 144, "y1": 288, @@ -10574,6 +11811,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 145, + "mark": null, "x": 145, "y0": 145, "y1": 290, @@ -10582,6 +11820,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 146, + "mark": null, "x": 146, "y0": 146, "y1": 292, @@ -10590,6 +11829,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 147, + "mark": null, "x": 147, "y0": 147, "y1": 294, @@ -10598,6 +11838,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 148, + "mark": null, "x": 148, "y0": 148, "y1": 296, @@ -10606,6 +11847,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 149, + "mark": null, "x": 149, "y0": 149, "y1": 298, @@ -10614,6 +11856,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 150, + "mark": null, "x": 150, "y0": 150, "y1": 300, @@ -10622,6 +11865,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 151, + "mark": null, "x": 151, "y0": 151, "y1": 302, @@ -10630,6 +11874,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 152, + "mark": null, "x": 152, "y0": 152, "y1": 304, @@ -10638,6 +11883,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 153, + "mark": null, "x": 153, "y0": 153, "y1": 306, @@ -10646,6 +11892,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 154, + "mark": null, "x": 154, "y0": 154, "y1": 308, @@ -10654,6 +11901,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 155, + "mark": null, "x": 155, "y0": 155, "y1": 310, @@ -10662,6 +11910,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 156, + "mark": null, "x": 156, "y0": 156, "y1": 312, @@ -10670,6 +11919,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 157, + "mark": null, "x": 157, "y0": 157, "y1": 314, @@ -10678,6 +11928,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 158, + "mark": null, "x": 158, "y0": 158, "y1": 316, @@ -10686,6 +11937,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 159, + "mark": null, "x": 159, "y0": 159, "y1": 318, @@ -10694,6 +11946,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 160, + "mark": null, "x": 160, "y0": 160, "y1": 320, @@ -10702,6 +11955,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 161, + "mark": null, "x": 161, "y0": 161, "y1": 322, @@ -10710,6 +11964,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 162, + "mark": null, "x": 162, "y0": 162, "y1": 324, @@ -10718,6 +11973,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 163, + "mark": null, "x": 163, "y0": 163, "y1": 326, @@ -10726,6 +11982,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 164, + "mark": null, "x": 164, "y0": 164, "y1": 328, @@ -10734,6 +11991,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 165, + "mark": null, "x": 165, "y0": 165, "y1": 330, @@ -10742,6 +12000,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 166, + "mark": null, "x": 166, "y0": 166, "y1": 332, @@ -10750,6 +12009,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 167, + "mark": null, "x": 167, "y0": 167, "y1": 334, @@ -10758,6 +12018,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 168, + "mark": null, "x": 168, "y0": 168, "y1": 336, @@ -10766,6 +12027,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 169, + "mark": null, "x": 169, "y0": 169, "y1": 338, @@ -10774,6 +12036,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 170, + "mark": null, "x": 170, "y0": 170, "y1": 340, @@ -10782,6 +12045,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 171, + "mark": null, "x": 171, "y0": 171, "y1": 342, @@ -10790,6 +12054,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 172, + "mark": null, "x": 172, "y0": 172, "y1": 344, @@ -10798,6 +12063,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 173, + "mark": null, "x": 173, "y0": 173, "y1": 346, @@ -10806,6 +12072,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 174, + "mark": null, "x": 174, "y0": 174, "y1": 348, @@ -10814,6 +12081,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 175, + "mark": null, "x": 175, "y0": 175, "y1": 350, @@ -10822,6 +12090,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 176, + "mark": null, "x": 176, "y0": 176, "y1": 352, @@ -10830,6 +12099,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 177, + "mark": null, "x": 177, "y0": 177, "y1": 354, @@ -10838,6 +12108,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 178, + "mark": null, "x": 178, "y0": 178, "y1": 356, @@ -10846,6 +12117,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 179, + "mark": null, "x": 179, "y0": 179, "y1": 358, @@ -10854,6 +12126,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 180, + "mark": null, "x": 180, "y0": 180, "y1": 360, @@ -10862,6 +12135,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 181, + "mark": null, "x": 181, "y0": 181, "y1": 362, @@ -10870,6 +12144,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 182, + "mark": null, "x": 182, "y0": 182, "y1": 364, @@ -10878,6 +12153,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 183, + "mark": null, "x": 183, "y0": 183, "y1": 366, @@ -10886,6 +12162,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 184, + "mark": null, "x": 184, "y0": 184, "y1": 368, @@ -10894,6 +12171,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 185, + "mark": null, "x": 185, "y0": 185, "y1": 370, @@ -10902,6 +12180,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 186, + "mark": null, "x": 186, "y0": 186, "y1": 372, @@ -10910,6 +12189,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 187, + "mark": null, "x": 187, "y0": 187, "y1": 374, @@ -10918,6 +12198,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 188, + "mark": null, "x": 188, "y0": 188, "y1": 376, @@ -10926,6 +12207,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 189, + "mark": null, "x": 189, "y0": 189, "y1": 378, @@ -10934,6 +12216,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 190, + "mark": null, "x": 190, "y0": 190, "y1": 380, @@ -10942,6 +12225,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 191, + "mark": null, "x": 191, "y0": 191, "y1": 382, @@ -10950,6 +12234,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 192, + "mark": null, "x": 192, "y0": 192, "y1": 384, @@ -10958,6 +12243,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 193, + "mark": null, "x": 193, "y0": 193, "y1": 386, @@ -10966,6 +12252,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 194, + "mark": null, "x": 194, "y0": 194, "y1": 388, @@ -10974,6 +12261,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 195, + "mark": null, "x": 195, "y0": 195, "y1": 390, @@ -10982,6 +12270,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 196, + "mark": null, "x": 196, "y0": 196, "y1": 392, @@ -10990,6 +12279,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 197, + "mark": null, "x": 197, "y0": 197, "y1": 394, @@ -10998,6 +12288,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 198, + "mark": null, "x": 198, "y0": 198, "y1": 396, @@ -11006,6 +12297,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 199, + "mark": null, "x": 199, "y0": 199, "y1": 398, @@ -11014,6 +12306,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 200, + "mark": null, "x": 200, "y0": 200, "y1": 400, @@ -11022,6 +12315,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 201, + "mark": null, "x": 201, "y0": 201, "y1": 402, @@ -11030,6 +12324,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 202, + "mark": null, "x": 202, "y0": 202, "y1": 404, @@ -11038,6 +12333,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 203, + "mark": null, "x": 203, "y0": 203, "y1": 406, @@ -11046,6 +12342,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 204, + "mark": null, "x": 204, "y0": 204, "y1": 408, @@ -11054,6 +12351,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 205, + "mark": null, "x": 205, "y0": 205, "y1": 410, @@ -11062,6 +12360,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 206, + "mark": null, "x": 206, "y0": 206, "y1": 412, @@ -11070,6 +12369,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 207, + "mark": null, "x": 207, "y0": 207, "y1": 414, @@ -11078,6 +12378,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 208, + "mark": null, "x": 208, "y0": 208, "y1": 416, @@ -11086,6 +12387,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 209, + "mark": null, "x": 209, "y0": 209, "y1": 418, @@ -11094,6 +12396,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 210, + "mark": null, "x": 210, "y0": 210, "y1": 420, @@ -11102,6 +12405,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 211, + "mark": null, "x": 211, "y0": 211, "y1": 422, @@ -11110,6 +12414,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 212, + "mark": null, "x": 212, "y0": 212, "y1": 424, @@ -11118,6 +12423,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 213, + "mark": null, "x": 213, "y0": 213, "y1": 426, @@ -11126,6 +12432,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 214, + "mark": null, "x": 214, "y0": 214, "y1": 428, @@ -11134,6 +12441,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 215, + "mark": null, "x": 215, "y0": 215, "y1": 430, @@ -11142,6 +12450,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 216, + "mark": null, "x": 216, "y0": 216, "y1": 432, @@ -11150,6 +12459,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 217, + "mark": null, "x": 217, "y0": 217, "y1": 434, @@ -11158,6 +12468,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 218, + "mark": null, "x": 218, "y0": 218, "y1": 436, @@ -11166,6 +12477,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 219, + "mark": null, "x": 219, "y0": 219, "y1": 438, @@ -11174,6 +12486,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 220, + "mark": null, "x": 220, "y0": 220, "y1": 440, @@ -11182,6 +12495,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 221, + "mark": null, "x": 221, "y0": 221, "y1": 442, @@ -11190,6 +12504,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 222, + "mark": null, "x": 222, "y0": 222, "y1": 444, @@ -11198,6 +12513,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 223, + "mark": null, "x": 223, "y0": 223, "y1": 446, @@ -11206,6 +12522,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 224, + "mark": null, "x": 224, "y0": 224, "y1": 448, @@ -11214,6 +12531,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 225, + "mark": null, "x": 225, "y0": 225, "y1": 450, @@ -11222,6 +12540,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 226, + "mark": null, "x": 226, "y0": 226, "y1": 452, @@ -11230,6 +12549,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 227, + "mark": null, "x": 227, "y0": 227, "y1": 454, @@ -11238,6 +12558,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 228, + "mark": null, "x": 228, "y0": 228, "y1": 456, @@ -11246,6 +12567,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 229, + "mark": null, "x": 229, "y0": 229, "y1": 458, @@ -11254,6 +12576,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 230, + "mark": null, "x": 230, "y0": 230, "y1": 460, @@ -11262,6 +12585,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 231, + "mark": null, "x": 231, "y0": 231, "y1": 462, @@ -11270,6 +12594,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 232, + "mark": null, "x": 232, "y0": 232, "y1": 464, @@ -11278,6 +12603,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 233, + "mark": null, "x": 233, "y0": 233, "y1": 466, @@ -11286,6 +12612,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 234, + "mark": null, "x": 234, "y0": 234, "y1": 468, @@ -11294,6 +12621,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 235, + "mark": null, "x": 235, "y0": 235, "y1": 470, @@ -11302,6 +12630,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 236, + "mark": null, "x": 236, "y0": 236, "y1": 472, @@ -11310,6 +12639,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 237, + "mark": null, "x": 237, "y0": 237, "y1": 474, @@ -11318,6 +12648,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 238, + "mark": null, "x": 238, "y0": 238, "y1": 476, @@ -11326,6 +12657,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 239, + "mark": null, "x": 239, "y0": 239, "y1": 478, @@ -11334,6 +12666,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 240, + "mark": null, "x": 240, "y0": 240, "y1": 480, @@ -11342,6 +12675,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 241, + "mark": null, "x": 241, "y0": 241, "y1": 482, @@ -11350,6 +12684,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 242, + "mark": null, "x": 242, "y0": 242, "y1": 484, @@ -11358,6 +12693,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 243, + "mark": null, "x": 243, "y0": 243, "y1": 486, @@ -11366,6 +12702,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 244, + "mark": null, "x": 244, "y0": 244, "y1": 488, @@ -11374,6 +12711,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 245, + "mark": null, "x": 245, "y0": 245, "y1": 490, @@ -11382,6 +12720,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 246, + "mark": null, "x": 246, "y0": 246, "y1": 492, @@ -11390,6 +12729,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 247, + "mark": null, "x": 247, "y0": 247, "y1": 494, @@ -11398,6 +12738,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 248, + "mark": null, "x": 248, "y0": 248, "y1": 496, @@ -11406,6 +12747,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 249, + "mark": null, "x": 249, "y0": 249, "y1": 498, @@ -11414,6 +12756,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 250, + "mark": null, "x": 250, "y0": 250, "y1": 500, @@ -11422,6 +12765,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 251, + "mark": null, "x": 251, "y0": 251, "y1": 502, @@ -11430,6 +12774,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 252, + "mark": null, "x": 252, "y0": 252, "y1": 504, @@ -11438,6 +12783,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 253, + "mark": null, "x": 253, "y0": 253, "y1": 506, @@ -11446,6 +12792,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 254, + "mark": null, "x": 254, "y0": 254, "y1": 508, @@ -11454,6 +12801,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 255, + "mark": null, "x": 255, "y0": 255, "y1": 510, @@ -11462,6 +12810,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 256, + "mark": null, "x": 256, "y0": 256, "y1": 512, @@ -11470,6 +12819,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 257, + "mark": null, "x": 257, "y0": 257, "y1": 514, @@ -11478,6 +12828,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 258, + "mark": null, "x": 258, "y0": 258, "y1": 516, @@ -11486,6 +12837,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 259, + "mark": null, "x": 259, "y0": 259, "y1": 518, @@ -11494,6 +12846,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 260, + "mark": null, "x": 260, "y0": 260, "y1": 520, @@ -11502,6 +12855,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 261, + "mark": null, "x": 261, "y0": 261, "y1": 522, @@ -11510,6 +12864,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 262, + "mark": null, "x": 262, "y0": 262, "y1": 524, @@ -11518,6 +12873,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 263, + "mark": null, "x": 263, "y0": 263, "y1": 526, @@ -11526,6 +12882,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 264, + "mark": null, "x": 264, "y0": 264, "y1": 528, @@ -11534,6 +12891,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 265, + "mark": null, "x": 265, "y0": 265, "y1": 530, @@ -11542,6 +12900,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 266, + "mark": null, "x": 266, "y0": 266, "y1": 532, @@ -11550,6 +12909,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 267, + "mark": null, "x": 267, "y0": 267, "y1": 534, @@ -11558,6 +12918,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 268, + "mark": null, "x": 268, "y0": 268, "y1": 536, @@ -11566,6 +12927,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 269, + "mark": null, "x": 269, "y0": 269, "y1": 538, @@ -11574,6 +12936,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 270, + "mark": null, "x": 270, "y0": 270, "y1": 540, @@ -11582,6 +12945,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 271, + "mark": null, "x": 271, "y0": 271, "y1": 542, @@ -11590,6 +12954,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 272, + "mark": null, "x": 272, "y0": 272, "y1": 544, @@ -11598,6 +12963,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 273, + "mark": null, "x": 273, "y0": 273, "y1": 546, @@ -11606,6 +12972,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 274, + "mark": null, "x": 274, "y0": 274, "y1": 548, @@ -11614,6 +12981,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 275, + "mark": null, "x": 275, "y0": 275, "y1": 550, @@ -11622,6 +12990,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 276, + "mark": null, "x": 276, "y0": 276, "y1": 552, @@ -11630,6 +12999,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 277, + "mark": null, "x": 277, "y0": 277, "y1": 554, @@ -11638,6 +13008,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 278, + "mark": null, "x": 278, "y0": 278, "y1": 556, @@ -11646,6 +13017,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 279, + "mark": null, "x": 279, "y0": 279, "y1": 558, @@ -11654,6 +13026,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 280, + "mark": null, "x": 280, "y0": 280, "y1": 560, @@ -11662,6 +13035,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 281, + "mark": null, "x": 281, "y0": 281, "y1": 562, @@ -11670,6 +13044,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 282, + "mark": null, "x": 282, "y0": 282, "y1": 564, @@ -11678,6 +13053,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 283, + "mark": null, "x": 283, "y0": 283, "y1": 566, @@ -11686,6 +13062,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 284, + "mark": null, "x": 284, "y0": 284, "y1": 568, @@ -11694,6 +13071,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 285, + "mark": null, "x": 285, "y0": 285, "y1": 570, @@ -11702,6 +13080,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 286, + "mark": null, "x": 286, "y0": 286, "y1": 572, @@ -11710,6 +13089,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 287, + "mark": null, "x": 287, "y0": 287, "y1": 574, @@ -11718,6 +13098,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 288, + "mark": null, "x": 288, "y0": 288, "y1": 576, @@ -11726,6 +13107,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 289, + "mark": null, "x": 289, "y0": 289, "y1": 578, @@ -11734,6 +13116,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 290, + "mark": null, "x": 290, "y0": 290, "y1": 580, @@ -11742,6 +13125,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 291, + "mark": null, "x": 291, "y0": 291, "y1": 582, @@ -11750,6 +13134,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 292, + "mark": null, "x": 292, "y0": 292, "y1": 584, @@ -11758,6 +13143,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 293, + "mark": null, "x": 293, "y0": 293, "y1": 586, @@ -11766,6 +13152,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 294, + "mark": null, "x": 294, "y0": 294, "y1": 588, @@ -11774,6 +13161,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 295, + "mark": null, "x": 295, "y0": 295, "y1": 590, @@ -11782,6 +13170,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 296, + "mark": null, "x": 296, "y0": 296, "y1": 592, @@ -11790,6 +13179,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 297, + "mark": null, "x": 297, "y0": 297, "y1": 594, @@ -11798,6 +13188,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 298, + "mark": null, "x": 298, "y0": 298, "y1": 596, @@ -11806,6 +13197,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 299, + "mark": null, "x": 299, "y0": 299, "y1": 598, @@ -11814,6 +13206,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 300, + "mark": null, "x": 300, "y0": 300, "y1": 600, @@ -11822,6 +13215,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 301, + "mark": null, "x": 301, "y0": 301, "y1": 602, @@ -11830,6 +13224,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 302, + "mark": null, "x": 302, "y0": 302, "y1": 604, @@ -11838,6 +13233,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 303, + "mark": null, "x": 303, "y0": 303, "y1": 606, @@ -11846,6 +13242,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 304, + "mark": null, "x": 304, "y0": 304, "y1": 608, @@ -11854,6 +13251,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 305, + "mark": null, "x": 305, "y0": 305, "y1": 610, @@ -11862,6 +13260,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 306, + "mark": null, "x": 306, "y0": 306, "y1": 612, @@ -11870,6 +13269,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 307, + "mark": null, "x": 307, "y0": 307, "y1": 614, @@ -11878,6 +13278,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 308, + "mark": null, "x": 308, "y0": 308, "y1": 616, @@ -11886,6 +13287,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 309, + "mark": null, "x": 309, "y0": 309, "y1": 618, @@ -11894,6 +13296,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 310, + "mark": null, "x": 310, "y0": 310, "y1": 620, @@ -11902,6 +13305,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 311, + "mark": null, "x": 311, "y0": 311, "y1": 622, @@ -11910,6 +13314,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 312, + "mark": null, "x": 312, "y0": 312, "y1": 624, @@ -11918,6 +13323,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 313, + "mark": null, "x": 313, "y0": 313, "y1": 626, @@ -11926,6 +13332,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 314, + "mark": null, "x": 314, "y0": 314, "y1": 628, @@ -11934,6 +13341,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 315, + "mark": null, "x": 315, "y0": 315, "y1": 630, @@ -11942,6 +13350,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 316, + "mark": null, "x": 316, "y0": 316, "y1": 632, @@ -11950,6 +13359,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 317, + "mark": null, "x": 317, "y0": 317, "y1": 634, @@ -11958,6 +13368,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 318, + "mark": null, "x": 318, "y0": 318, "y1": 636, @@ -11966,6 +13377,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 319, + "mark": null, "x": 319, "y0": 319, "y1": 638, @@ -11974,6 +13386,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 320, + "mark": null, "x": 320, "y0": 320, "y1": 640, @@ -11982,6 +13395,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 321, + "mark": null, "x": 321, "y0": 321, "y1": 642, @@ -11990,6 +13404,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 322, + "mark": null, "x": 322, "y0": 322, "y1": 644, @@ -11998,6 +13413,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 323, + "mark": null, "x": 323, "y0": 323, "y1": 646, @@ -12006,6 +13422,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 324, + "mark": null, "x": 324, "y0": 324, "y1": 648, @@ -12014,6 +13431,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 325, + "mark": null, "x": 325, "y0": 325, "y1": 650, @@ -12022,6 +13440,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 326, + "mark": null, "x": 326, "y0": 326, "y1": 652, @@ -12030,6 +13449,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 327, + "mark": null, "x": 327, "y0": 327, "y1": 654, @@ -12038,6 +13458,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 328, + "mark": null, "x": 328, "y0": 328, "y1": 656, @@ -12046,6 +13467,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 329, + "mark": null, "x": 329, "y0": 329, "y1": 658, @@ -12054,6 +13476,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 330, + "mark": null, "x": 330, "y0": 330, "y1": 660, @@ -12062,6 +13485,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 331, + "mark": null, "x": 331, "y0": 331, "y1": 662, @@ -12070,6 +13494,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 332, + "mark": null, "x": 332, "y0": 332, "y1": 664, @@ -12078,6 +13503,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 333, + "mark": null, "x": 333, "y0": 333, "y1": 666, @@ -12086,6 +13512,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 334, + "mark": null, "x": 334, "y0": 334, "y1": 668, @@ -12094,6 +13521,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 335, + "mark": null, "x": 335, "y0": 335, "y1": 670, @@ -12102,6 +13530,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 336, + "mark": null, "x": 336, "y0": 336, "y1": 672, @@ -12110,6 +13539,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 337, + "mark": null, "x": 337, "y0": 337, "y1": 674, @@ -12118,6 +13548,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 338, + "mark": null, "x": 338, "y0": 338, "y1": 676, @@ -12126,6 +13557,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 339, + "mark": null, "x": 339, "y0": 339, "y1": 678, @@ -12134,6 +13566,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 340, + "mark": null, "x": 340, "y0": 340, "y1": 680, @@ -12142,6 +13575,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 341, + "mark": null, "x": 341, "y0": 341, "y1": 682, @@ -12150,6 +13584,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 342, + "mark": null, "x": 342, "y0": 342, "y1": 684, @@ -12158,6 +13593,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 343, + "mark": null, "x": 343, "y0": 343, "y1": 686, @@ -12166,6 +13602,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 344, + "mark": null, "x": 344, "y0": 344, "y1": 688, @@ -12174,6 +13611,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 345, + "mark": null, "x": 345, "y0": 345, "y1": 690, @@ -12182,6 +13620,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 346, + "mark": null, "x": 346, "y0": 346, "y1": 692, @@ -12190,6 +13629,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 347, + "mark": null, "x": 347, "y0": 347, "y1": 694, @@ -12198,6 +13638,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 348, + "mark": null, "x": 348, "y0": 348, "y1": 696, @@ -12206,6 +13647,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 349, + "mark": null, "x": 349, "y0": 349, "y1": 698, @@ -12214,6 +13656,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 350, + "mark": null, "x": 350, "y0": 350, "y1": 700, @@ -12222,6 +13665,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 351, + "mark": null, "x": 351, "y0": 351, "y1": 702, @@ -12230,6 +13674,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 352, + "mark": null, "x": 352, "y0": 352, "y1": 704, @@ -12238,6 +13683,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 353, + "mark": null, "x": 353, "y0": 353, "y1": 706, @@ -12246,6 +13692,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 354, + "mark": null, "x": 354, "y0": 354, "y1": 708, @@ -12254,6 +13701,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 355, + "mark": null, "x": 355, "y0": 355, "y1": 710, @@ -12262,6 +13710,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 356, + "mark": null, "x": 356, "y0": 356, "y1": 712, @@ -12270,6 +13719,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 357, + "mark": null, "x": 357, "y0": 357, "y1": 714, @@ -12278,6 +13728,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 358, + "mark": null, "x": 358, "y0": 358, "y1": 716, @@ -12286,6 +13737,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 359, + "mark": null, "x": 359, "y0": 359, "y1": 718, @@ -12294,6 +13746,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 360, + "mark": null, "x": 360, "y0": 360, "y1": 720, @@ -12302,6 +13755,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 361, + "mark": null, "x": 361, "y0": 361, "y1": 722, @@ -12310,6 +13764,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 362, + "mark": null, "x": 362, "y0": 362, "y1": 724, @@ -12318,6 +13773,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 363, + "mark": null, "x": 363, "y0": 363, "y1": 726, @@ -12326,6 +13782,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 364, + "mark": null, "x": 364, "y0": 364, "y1": 728, @@ -12334,6 +13791,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 365, + "mark": null, "x": 365, "y0": 365, "y1": 730, @@ -12342,6 +13800,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 366, + "mark": null, "x": 366, "y0": 366, "y1": 732, @@ -12350,6 +13809,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 367, + "mark": null, "x": 367, "y0": 367, "y1": 734, @@ -12358,6 +13818,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 368, + "mark": null, "x": 368, "y0": 368, "y1": 736, @@ -12366,6 +13827,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 369, + "mark": null, "x": 369, "y0": 369, "y1": 738, @@ -12374,6 +13836,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 370, + "mark": null, "x": 370, "y0": 370, "y1": 740, @@ -12382,6 +13845,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 371, + "mark": null, "x": 371, "y0": 371, "y1": 742, @@ -12390,6 +13854,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 372, + "mark": null, "x": 372, "y0": 372, "y1": 744, @@ -12398,6 +13863,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 373, + "mark": null, "x": 373, "y0": 373, "y1": 746, @@ -12406,6 +13872,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 374, + "mark": null, "x": 374, "y0": 374, "y1": 748, @@ -12414,6 +13881,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 375, + "mark": null, "x": 375, "y0": 375, "y1": 750, @@ -12422,6 +13890,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 376, + "mark": null, "x": 376, "y0": 376, "y1": 752, @@ -12430,6 +13899,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 377, + "mark": null, "x": 377, "y0": 377, "y1": 754, @@ -12438,6 +13908,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 378, + "mark": null, "x": 378, "y0": 378, "y1": 756, @@ -12446,6 +13917,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 379, + "mark": null, "x": 379, "y0": 379, "y1": 758, @@ -12454,6 +13926,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 380, + "mark": null, "x": 380, "y0": 380, "y1": 760, @@ -12462,6 +13935,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 381, + "mark": null, "x": 381, "y0": 381, "y1": 762, @@ -12470,6 +13944,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 382, + "mark": null, "x": 382, "y0": 382, "y1": 764, @@ -12478,6 +13953,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 383, + "mark": null, "x": 383, "y0": 383, "y1": 766, @@ -12486,6 +13962,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 384, + "mark": null, "x": 384, "y0": 384, "y1": 768, @@ -12494,6 +13971,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 385, + "mark": null, "x": 385, "y0": 385, "y1": 770, @@ -12502,6 +13980,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 386, + "mark": null, "x": 386, "y0": 386, "y1": 772, @@ -12510,6 +13989,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 387, + "mark": null, "x": 387, "y0": 387, "y1": 774, @@ -12518,6 +13998,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 388, + "mark": null, "x": 388, "y0": 388, "y1": 776, @@ -12526,6 +14007,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 389, + "mark": null, "x": 389, "y0": 389, "y1": 778, @@ -12534,6 +14016,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 390, + "mark": null, "x": 390, "y0": 390, "y1": 780, @@ -12542,6 +14025,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 391, + "mark": null, "x": 391, "y0": 391, "y1": 782, @@ -12550,6 +14034,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 392, + "mark": null, "x": 392, "y0": 392, "y1": 784, @@ -12558,6 +14043,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 393, + "mark": null, "x": 393, "y0": 393, "y1": 786, @@ -12566,6 +14052,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 394, + "mark": null, "x": 394, "y0": 394, "y1": 788, @@ -12574,6 +14061,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 395, + "mark": null, "x": 395, "y0": 395, "y1": 790, @@ -12582,6 +14070,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 396, + "mark": null, "x": 396, "y0": 396, "y1": 792, @@ -12590,6 +14079,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 397, + "mark": null, "x": 397, "y0": 397, "y1": 794, @@ -12598,6 +14088,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 398, + "mark": null, "x": 398, "y0": 398, "y1": 796, @@ -12606,6 +14097,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 399, + "mark": null, "x": 399, "y0": 399, "y1": 798, @@ -12614,6 +14106,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 400, + "mark": null, "x": 400, "y0": 400, "y1": 800, @@ -12622,6 +14115,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 401, + "mark": null, "x": 401, "y0": 401, "y1": 802, @@ -12630,6 +14124,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 402, + "mark": null, "x": 402, "y0": 402, "y1": 804, @@ -12638,6 +14133,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 403, + "mark": null, "x": 403, "y0": 403, "y1": 806, @@ -12646,6 +14142,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 404, + "mark": null, "x": 404, "y0": 404, "y1": 808, @@ -12654,6 +14151,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 405, + "mark": null, "x": 405, "y0": 405, "y1": 810, @@ -12662,6 +14160,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 406, + "mark": null, "x": 406, "y0": 406, "y1": 812, @@ -12670,6 +14169,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 407, + "mark": null, "x": 407, "y0": 407, "y1": 814, @@ -12678,6 +14178,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 408, + "mark": null, "x": 408, "y0": 408, "y1": 816, @@ -12686,6 +14187,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 409, + "mark": null, "x": 409, "y0": 409, "y1": 818, @@ -12694,6 +14196,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 410, + "mark": null, "x": 410, "y0": 410, "y1": 820, @@ -12702,6 +14205,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 411, + "mark": null, "x": 411, "y0": 411, "y1": 822, @@ -12710,6 +14214,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 412, + "mark": null, "x": 412, "y0": 412, "y1": 824, @@ -12718,6 +14223,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 413, + "mark": null, "x": 413, "y0": 413, "y1": 826, @@ -12726,6 +14232,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 414, + "mark": null, "x": 414, "y0": 414, "y1": 828, @@ -12734,6 +14241,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 415, + "mark": null, "x": 415, "y0": 415, "y1": 830, @@ -12742,6 +14250,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 416, + "mark": null, "x": 416, "y0": 416, "y1": 832, @@ -12750,6 +14259,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 417, + "mark": null, "x": 417, "y0": 417, "y1": 834, @@ -12758,6 +14268,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 418, + "mark": null, "x": 418, "y0": 418, "y1": 836, @@ -12766,6 +14277,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 419, + "mark": null, "x": 419, "y0": 419, "y1": 838, @@ -12774,6 +14286,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 420, + "mark": null, "x": 420, "y0": 420, "y1": 840, @@ -12782,6 +14295,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 421, + "mark": null, "x": 421, "y0": 421, "y1": 842, @@ -12790,6 +14304,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 422, + "mark": null, "x": 422, "y0": 422, "y1": 844, @@ -12798,6 +14313,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 423, + "mark": null, "x": 423, "y0": 423, "y1": 846, @@ -12806,6 +14322,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 424, + "mark": null, "x": 424, "y0": 424, "y1": 848, @@ -12814,6 +14331,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 425, + "mark": null, "x": 425, "y0": 425, "y1": 850, @@ -12822,6 +14340,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 426, + "mark": null, "x": 426, "y0": 426, "y1": 852, @@ -12830,6 +14349,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 427, + "mark": null, "x": 427, "y0": 427, "y1": 854, @@ -12838,6 +14358,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 428, + "mark": null, "x": 428, "y0": 428, "y1": 856, @@ -12846,6 +14367,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 429, + "mark": null, "x": 429, "y0": 429, "y1": 858, @@ -12854,6 +14376,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 430, + "mark": null, "x": 430, "y0": 430, "y1": 860, @@ -12862,6 +14385,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 431, + "mark": null, "x": 431, "y0": 431, "y1": 862, @@ -12870,6 +14394,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 432, + "mark": null, "x": 432, "y0": 432, "y1": 864, @@ -12878,6 +14403,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 433, + "mark": null, "x": 433, "y0": 433, "y1": 866, @@ -12886,6 +14412,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 434, + "mark": null, "x": 434, "y0": 434, "y1": 868, @@ -12894,6 +14421,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 435, + "mark": null, "x": 435, "y0": 435, "y1": 870, @@ -12902,6 +14430,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 436, + "mark": null, "x": 436, "y0": 436, "y1": 872, @@ -12910,6 +14439,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 437, + "mark": null, "x": 437, "y0": 437, "y1": 874, @@ -12918,6 +14448,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 438, + "mark": null, "x": 438, "y0": 438, "y1": 876, @@ -12926,6 +14457,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 439, + "mark": null, "x": 439, "y0": 439, "y1": 878, @@ -12934,6 +14466,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 440, + "mark": null, "x": 440, "y0": 440, "y1": 880, @@ -12942,6 +14475,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 441, + "mark": null, "x": 441, "y0": 441, "y1": 882, @@ -12950,6 +14484,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 442, + "mark": null, "x": 442, "y0": 442, "y1": 884, @@ -12958,6 +14493,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 443, + "mark": null, "x": 443, "y0": 443, "y1": 886, @@ -12966,6 +14502,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 444, + "mark": null, "x": 444, "y0": 444, "y1": 888, @@ -12974,6 +14511,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 445, + "mark": null, "x": 445, "y0": 445, "y1": 890, @@ -12982,6 +14520,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 446, + "mark": null, "x": 446, "y0": 446, "y1": 892, @@ -12990,6 +14529,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 447, + "mark": null, "x": 447, "y0": 447, "y1": 894, @@ -12998,6 +14538,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 448, + "mark": null, "x": 448, "y0": 448, "y1": 896, @@ -13006,6 +14547,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 449, + "mark": null, "x": 449, "y0": 449, "y1": 898, @@ -13014,6 +14556,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 450, + "mark": null, "x": 450, "y0": 450, "y1": 900, @@ -13022,6 +14565,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 451, + "mark": null, "x": 451, "y0": 451, "y1": 902, @@ -13030,6 +14574,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 452, + "mark": null, "x": 452, "y0": 452, "y1": 904, @@ -13038,6 +14583,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 453, + "mark": null, "x": 453, "y0": 453, "y1": 906, @@ -13046,6 +14592,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 454, + "mark": null, "x": 454, "y0": 454, "y1": 908, @@ -13054,6 +14601,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 455, + "mark": null, "x": 455, "y0": 455, "y1": 910, @@ -13062,6 +14610,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 456, + "mark": null, "x": 456, "y0": 456, "y1": 912, @@ -13070,6 +14619,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 457, + "mark": null, "x": 457, "y0": 457, "y1": 914, @@ -13078,6 +14628,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 458, + "mark": null, "x": 458, "y0": 458, "y1": 916, @@ -13086,6 +14637,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 459, + "mark": null, "x": 459, "y0": 459, "y1": 918, @@ -13094,6 +14646,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 460, + "mark": null, "x": 460, "y0": 460, "y1": 920, @@ -13102,6 +14655,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 461, + "mark": null, "x": 461, "y0": 461, "y1": 922, @@ -13110,6 +14664,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 462, + "mark": null, "x": 462, "y0": 462, "y1": 924, @@ -13118,6 +14673,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 463, + "mark": null, "x": 463, "y0": 463, "y1": 926, @@ -13126,6 +14682,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 464, + "mark": null, "x": 464, "y0": 464, "y1": 928, @@ -13134,6 +14691,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 465, + "mark": null, "x": 465, "y0": 465, "y1": 930, @@ -13142,6 +14700,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 466, + "mark": null, "x": 466, "y0": 466, "y1": 932, @@ -13150,6 +14709,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 467, + "mark": null, "x": 467, "y0": 467, "y1": 934, @@ -13158,6 +14718,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 468, + "mark": null, "x": 468, "y0": 468, "y1": 936, @@ -13166,6 +14727,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 469, + "mark": null, "x": 469, "y0": 469, "y1": 938, @@ -13174,6 +14736,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 470, + "mark": null, "x": 470, "y0": 470, "y1": 940, @@ -13182,6 +14745,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 471, + "mark": null, "x": 471, "y0": 471, "y1": 942, @@ -13190,6 +14754,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 472, + "mark": null, "x": 472, "y0": 472, "y1": 944, @@ -13198,6 +14763,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 473, + "mark": null, "x": 473, "y0": 473, "y1": 946, @@ -13206,6 +14772,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 474, + "mark": null, "x": 474, "y0": 474, "y1": 948, @@ -13214,6 +14781,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 475, + "mark": null, "x": 475, "y0": 475, "y1": 950, @@ -13222,6 +14790,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 476, + "mark": null, "x": 476, "y0": 476, "y1": 952, @@ -13230,6 +14799,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 477, + "mark": null, "x": 477, "y0": 477, "y1": 954, @@ -13238,6 +14808,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 478, + "mark": null, "x": 478, "y0": 478, "y1": 956, @@ -13246,6 +14817,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 479, + "mark": null, "x": 479, "y0": 479, "y1": 958, @@ -13254,6 +14826,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 480, + "mark": null, "x": 480, "y0": 480, "y1": 960, @@ -13262,6 +14835,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 481, + "mark": null, "x": 481, "y0": 481, "y1": 962, @@ -13270,6 +14844,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 482, + "mark": null, "x": 482, "y0": 482, "y1": 964, @@ -13278,6 +14853,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 483, + "mark": null, "x": 483, "y0": 483, "y1": 966, @@ -13286,6 +14862,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 484, + "mark": null, "x": 484, "y0": 484, "y1": 968, @@ -13294,6 +14871,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 485, + "mark": null, "x": 485, "y0": 485, "y1": 970, @@ -13302,6 +14880,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 486, + "mark": null, "x": 486, "y0": 486, "y1": 972, @@ -13310,6 +14889,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 487, + "mark": null, "x": 487, "y0": 487, "y1": 974, @@ -13318,6 +14898,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 488, + "mark": null, "x": 488, "y0": 488, "y1": 976, @@ -13326,6 +14907,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 489, + "mark": null, "x": 489, "y0": 489, "y1": 978, @@ -13334,6 +14916,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 490, + "mark": null, "x": 490, "y0": 490, "y1": 980, @@ -13342,6 +14925,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 491, + "mark": null, "x": 491, "y0": 491, "y1": 982, @@ -13350,6 +14934,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 492, + "mark": null, "x": 492, "y0": 492, "y1": 984, @@ -13358,6 +14943,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 493, + "mark": null, "x": 493, "y0": 493, "y1": 986, @@ -13366,6 +14952,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 494, + "mark": null, "x": 494, "y0": 494, "y1": 988, @@ -13374,6 +14961,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 495, + "mark": null, "x": 495, "y0": 495, "y1": 990, @@ -13382,6 +14970,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 496, + "mark": null, "x": 496, "y0": 496, "y1": 992, @@ -13390,6 +14979,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 497, + "mark": null, "x": 497, "y0": 497, "y1": 994, @@ -13398,6 +14988,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 498, + "mark": null, "x": 498, "y0": 498, "y1": 996, @@ -13406,6 +14997,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 499, + "mark": null, "x": 499, "y0": 499, "y1": 998, @@ -13414,6 +15006,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 500, + "mark": null, "x": 500, "y0": 500, "y1": 1000, @@ -13422,6 +15015,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 501, + "mark": null, "x": 501, "y0": 501, "y1": 1002, @@ -13430,6 +15024,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 502, + "mark": null, "x": 502, "y0": 502, "y1": 1004, @@ -13438,6 +15033,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 503, + "mark": null, "x": 503, "y0": 503, "y1": 1006, @@ -13446,6 +15042,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 504, + "mark": null, "x": 504, "y0": 504, "y1": 1008, @@ -13454,6 +15051,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 505, + "mark": null, "x": 505, "y0": 505, "y1": 1010, @@ -13462,6 +15060,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 506, + "mark": null, "x": 506, "y0": 506, "y1": 1012, @@ -13470,6 +15069,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 507, + "mark": null, "x": 507, "y0": 507, "y1": 1014, @@ -13478,6 +15078,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 508, + "mark": null, "x": 508, "y0": 508, "y1": 1016, @@ -13486,6 +15087,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 509, + "mark": null, "x": 509, "y0": 509, "y1": 1018, @@ -13494,6 +15096,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 510, + "mark": null, "x": 510, "y0": 510, "y1": 1020, @@ -13502,6 +15105,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 511, + "mark": null, "x": 511, "y0": 511, "y1": 1022, @@ -13510,6 +15114,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 512, + "mark": null, "x": 512, "y0": 512, "y1": 1024, @@ -13518,6 +15123,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 513, + "mark": null, "x": 513, "y0": 513, "y1": 1026, @@ -13526,6 +15132,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 514, + "mark": null, "x": 514, "y0": 514, "y1": 1028, @@ -13534,6 +15141,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 515, + "mark": null, "x": 515, "y0": 515, "y1": 1030, @@ -13542,6 +15150,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 516, + "mark": null, "x": 516, "y0": 516, "y1": 1032, @@ -13550,6 +15159,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 517, + "mark": null, "x": 517, "y0": 517, "y1": 1034, @@ -13558,6 +15168,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 518, + "mark": null, "x": 518, "y0": 518, "y1": 1036, @@ -13566,6 +15177,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 519, + "mark": null, "x": 519, "y0": 519, "y1": 1038, @@ -13574,6 +15186,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 520, + "mark": null, "x": 520, "y0": 520, "y1": 1040, @@ -13582,6 +15195,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 521, + "mark": null, "x": 521, "y0": 521, "y1": 1042, @@ -13590,6 +15204,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 522, + "mark": null, "x": 522, "y0": 522, "y1": 1044, @@ -13598,6 +15213,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 523, + "mark": null, "x": 523, "y0": 523, "y1": 1046, @@ -13606,6 +15222,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 524, + "mark": null, "x": 524, "y0": 524, "y1": 1048, @@ -13614,6 +15231,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 525, + "mark": null, "x": 525, "y0": 525, "y1": 1050, @@ -13622,6 +15240,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 526, + "mark": null, "x": 526, "y0": 526, "y1": 1052, @@ -13630,6 +15249,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 527, + "mark": null, "x": 527, "y0": 527, "y1": 1054, @@ -13638,6 +15258,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 528, + "mark": null, "x": 528, "y0": 528, "y1": 1056, @@ -13646,6 +15267,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 529, + "mark": null, "x": 529, "y0": 529, "y1": 1058, @@ -13654,6 +15276,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 530, + "mark": null, "x": 530, "y0": 530, "y1": 1060, @@ -13662,6 +15285,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 531, + "mark": null, "x": 531, "y0": 531, "y1": 1062, @@ -13670,6 +15294,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 532, + "mark": null, "x": 532, "y0": 532, "y1": 1064, @@ -13678,6 +15303,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 533, + "mark": null, "x": 533, "y0": 533, "y1": 1066, @@ -13686,6 +15312,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 534, + "mark": null, "x": 534, "y0": 534, "y1": 1068, @@ -13694,6 +15321,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 535, + "mark": null, "x": 535, "y0": 535, "y1": 1070, @@ -13702,6 +15330,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 536, + "mark": null, "x": 536, "y0": 536, "y1": 1072, @@ -13710,6 +15339,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 537, + "mark": null, "x": 537, "y0": 537, "y1": 1074, @@ -13718,6 +15348,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 538, + "mark": null, "x": 538, "y0": 538, "y1": 1076, @@ -13726,6 +15357,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 539, + "mark": null, "x": 539, "y0": 539, "y1": 1078, @@ -13734,6 +15366,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 540, + "mark": null, "x": 540, "y0": 540, "y1": 1080, @@ -13742,6 +15375,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 541, + "mark": null, "x": 541, "y0": 541, "y1": 1082, @@ -13750,6 +15384,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 542, + "mark": null, "x": 542, "y0": 542, "y1": 1084, @@ -13758,6 +15393,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 543, + "mark": null, "x": 543, "y0": 543, "y1": 1086, @@ -13766,6 +15402,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 544, + "mark": null, "x": 544, "y0": 544, "y1": 1088, @@ -13774,6 +15411,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 545, + "mark": null, "x": 545, "y0": 545, "y1": 1090, @@ -13782,6 +15420,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 546, + "mark": null, "x": 546, "y0": 546, "y1": 1092, @@ -13790,6 +15429,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 547, + "mark": null, "x": 547, "y0": 547, "y1": 1094, @@ -13798,6 +15438,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 548, + "mark": null, "x": 548, "y0": 548, "y1": 1096, @@ -13806,6 +15447,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 549, + "mark": null, "x": 549, "y0": 549, "y1": 1098, @@ -13814,6 +15456,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 550, + "mark": null, "x": 550, "y0": 550, "y1": 1100, @@ -13822,6 +15465,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 551, + "mark": null, "x": 551, "y0": 551, "y1": 1102, @@ -13830,6 +15474,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 552, + "mark": null, "x": 552, "y0": 552, "y1": 1104, @@ -13838,6 +15483,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 553, + "mark": null, "x": 553, "y0": 553, "y1": 1106, @@ -13846,6 +15492,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 554, + "mark": null, "x": 554, "y0": 554, "y1": 1108, @@ -13854,6 +15501,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 555, + "mark": null, "x": 555, "y0": 555, "y1": 1110, @@ -13862,6 +15510,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 556, + "mark": null, "x": 556, "y0": 556, "y1": 1112, @@ -13870,6 +15519,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 557, + "mark": null, "x": 557, "y0": 557, "y1": 1114, @@ -13878,6 +15528,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 558, + "mark": null, "x": 558, "y0": 558, "y1": 1116, @@ -13886,6 +15537,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 559, + "mark": null, "x": 559, "y0": 559, "y1": 1118, @@ -13894,6 +15546,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 560, + "mark": null, "x": 560, "y0": 560, "y1": 1120, @@ -13902,6 +15555,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 561, + "mark": null, "x": 561, "y0": 561, "y1": 1122, @@ -13910,6 +15564,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 562, + "mark": null, "x": 562, "y0": 562, "y1": 1124, @@ -13918,6 +15573,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 563, + "mark": null, "x": 563, "y0": 563, "y1": 1126, @@ -13926,6 +15582,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 564, + "mark": null, "x": 564, "y0": 564, "y1": 1128, @@ -13934,6 +15591,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 565, + "mark": null, "x": 565, "y0": 565, "y1": 1130, @@ -13942,6 +15600,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 566, + "mark": null, "x": 566, "y0": 566, "y1": 1132, @@ -13950,6 +15609,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 567, + "mark": null, "x": 567, "y0": 567, "y1": 1134, @@ -13958,6 +15618,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 568, + "mark": null, "x": 568, "y0": 568, "y1": 1136, @@ -13966,6 +15627,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 569, + "mark": null, "x": 569, "y0": 569, "y1": 1138, @@ -13974,6 +15636,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 570, + "mark": null, "x": 570, "y0": 570, "y1": 1140, @@ -13982,6 +15645,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 571, + "mark": null, "x": 571, "y0": 571, "y1": 1142, @@ -13990,6 +15654,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 572, + "mark": null, "x": 572, "y0": 572, "y1": 1144, @@ -13998,6 +15663,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 573, + "mark": null, "x": 573, "y0": 573, "y1": 1146, @@ -14006,6 +15672,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 574, + "mark": null, "x": 574, "y0": 574, "y1": 1148, @@ -14014,6 +15681,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 575, + "mark": null, "x": 575, "y0": 575, "y1": 1150, @@ -14022,6 +15690,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 576, + "mark": null, "x": 576, "y0": 576, "y1": 1152, @@ -14030,6 +15699,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 577, + "mark": null, "x": 577, "y0": 577, "y1": 1154, @@ -14038,6 +15708,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 578, + "mark": null, "x": 578, "y0": 578, "y1": 1156, @@ -14046,6 +15717,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 579, + "mark": null, "x": 579, "y0": 579, "y1": 1158, @@ -14054,6 +15726,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 580, + "mark": null, "x": 580, "y0": 580, "y1": 1160, @@ -14062,6 +15735,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 581, + "mark": null, "x": 581, "y0": 581, "y1": 1162, @@ -14070,6 +15744,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 582, + "mark": null, "x": 582, "y0": 582, "y1": 1164, @@ -14078,6 +15753,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 583, + "mark": null, "x": 583, "y0": 583, "y1": 1166, @@ -14086,6 +15762,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 584, + "mark": null, "x": 584, "y0": 584, "y1": 1168, @@ -14094,6 +15771,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 585, + "mark": null, "x": 585, "y0": 585, "y1": 1170, @@ -14102,6 +15780,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 586, + "mark": null, "x": 586, "y0": 586, "y1": 1172, @@ -14110,6 +15789,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 587, + "mark": null, "x": 587, "y0": 587, "y1": 1174, @@ -14118,6 +15798,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 588, + "mark": null, "x": 588, "y0": 588, "y1": 1176, @@ -14126,6 +15807,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 589, + "mark": null, "x": 589, "y0": 589, "y1": 1178, @@ -14134,6 +15816,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 590, + "mark": null, "x": 590, "y0": 590, "y1": 1180, @@ -14142,6 +15825,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 591, + "mark": null, "x": 591, "y0": 591, "y1": 1182, @@ -14150,6 +15834,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 592, + "mark": null, "x": 592, "y0": 592, "y1": 1184, @@ -14158,6 +15843,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 593, + "mark": null, "x": 593, "y0": 593, "y1": 1186, @@ -14166,6 +15852,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 594, + "mark": null, "x": 594, "y0": 594, "y1": 1188, @@ -14174,6 +15861,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 595, + "mark": null, "x": 595, "y0": 595, "y1": 1190, @@ -14182,6 +15870,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 596, + "mark": null, "x": 596, "y0": 596, "y1": 1192, @@ -14190,6 +15879,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 597, + "mark": null, "x": 597, "y0": 597, "y1": 1194, @@ -14198,6 +15888,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 598, + "mark": null, "x": 598, "y0": 598, "y1": 1196, @@ -14206,6 +15897,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 599, + "mark": null, "x": 599, "y0": 599, "y1": 1198, @@ -14214,6 +15906,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 600, + "mark": null, "x": 600, "y0": 600, "y1": 1200, @@ -14222,6 +15915,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 601, + "mark": null, "x": 601, "y0": 601, "y1": 1202, @@ -14230,6 +15924,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 602, + "mark": null, "x": 602, "y0": 602, "y1": 1204, @@ -14238,6 +15933,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 603, + "mark": null, "x": 603, "y0": 603, "y1": 1206, @@ -14246,6 +15942,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 604, + "mark": null, "x": 604, "y0": 604, "y1": 1208, @@ -14254,6 +15951,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 605, + "mark": null, "x": 605, "y0": 605, "y1": 1210, @@ -14262,6 +15960,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 606, + "mark": null, "x": 606, "y0": 606, "y1": 1212, @@ -14270,6 +15969,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 607, + "mark": null, "x": 607, "y0": 607, "y1": 1214, @@ -14278,6 +15978,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 608, + "mark": null, "x": 608, "y0": 608, "y1": 1216, @@ -14286,6 +15987,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 609, + "mark": null, "x": 609, "y0": 609, "y1": 1218, @@ -14294,6 +15996,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 610, + "mark": null, "x": 610, "y0": 610, "y1": 1220, @@ -14302,6 +16005,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 611, + "mark": null, "x": 611, "y0": 611, "y1": 1222, @@ -14310,6 +16014,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 612, + "mark": null, "x": 612, "y0": 612, "y1": 1224, @@ -14318,6 +16023,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 613, + "mark": null, "x": 613, "y0": 613, "y1": 1226, @@ -14326,6 +16032,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 614, + "mark": null, "x": 614, "y0": 614, "y1": 1228, @@ -14334,6 +16041,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 615, + "mark": null, "x": 615, "y0": 615, "y1": 1230, @@ -14342,6 +16050,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 616, + "mark": null, "x": 616, "y0": 616, "y1": 1232, @@ -14350,6 +16059,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 617, + "mark": null, "x": 617, "y0": 617, "y1": 1234, @@ -14358,6 +16068,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 618, + "mark": null, "x": 618, "y0": 618, "y1": 1236, @@ -14366,6 +16077,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 619, + "mark": null, "x": 619, "y0": 619, "y1": 1238, @@ -14374,6 +16086,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 620, + "mark": null, "x": 620, "y0": 620, "y1": 1240, @@ -14382,6 +16095,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 621, + "mark": null, "x": 621, "y0": 621, "y1": 1242, @@ -14390,6 +16104,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 622, + "mark": null, "x": 622, "y0": 622, "y1": 1244, @@ -14398,6 +16113,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 623, + "mark": null, "x": 623, "y0": 623, "y1": 1246, @@ -14406,6 +16122,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 624, + "mark": null, "x": 624, "y0": 624, "y1": 1248, @@ -14414,6 +16131,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 625, + "mark": null, "x": 625, "y0": 625, "y1": 1250, @@ -14422,6 +16140,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 626, + "mark": null, "x": 626, "y0": 626, "y1": 1252, @@ -14430,6 +16149,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 627, + "mark": null, "x": 627, "y0": 627, "y1": 1254, @@ -14438,6 +16158,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 628, + "mark": null, "x": 628, "y0": 628, "y1": 1256, @@ -14446,6 +16167,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 629, + "mark": null, "x": 629, "y0": 629, "y1": 1258, @@ -14454,6 +16176,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 630, + "mark": null, "x": 630, "y0": 630, "y1": 1260, @@ -14462,6 +16185,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 631, + "mark": null, "x": 631, "y0": 631, "y1": 1262, @@ -14470,6 +16194,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 632, + "mark": null, "x": 632, "y0": 632, "y1": 1264, @@ -14478,6 +16203,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 633, + "mark": null, "x": 633, "y0": 633, "y1": 1266, @@ -14486,6 +16212,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 634, + "mark": null, "x": 634, "y0": 634, "y1": 1268, @@ -14494,6 +16221,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 635, + "mark": null, "x": 635, "y0": 635, "y1": 1270, @@ -14502,6 +16230,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 636, + "mark": null, "x": 636, "y0": 636, "y1": 1272, @@ -14510,6 +16239,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 637, + "mark": null, "x": 637, "y0": 637, "y1": 1274, @@ -14518,6 +16248,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 638, + "mark": null, "x": 638, "y0": 638, "y1": 1276, @@ -14526,6 +16257,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 639, + "mark": null, "x": 639, "y0": 639, "y1": 1278, @@ -14534,6 +16266,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 640, + "mark": null, "x": 640, "y0": 640, "y1": 1280, @@ -14542,6 +16275,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 641, + "mark": null, "x": 641, "y0": 641, "y1": 1282, @@ -14550,6 +16284,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 642, + "mark": null, "x": 642, "y0": 642, "y1": 1284, @@ -14558,6 +16293,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 643, + "mark": null, "x": 643, "y0": 643, "y1": 1286, @@ -14566,6 +16302,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 644, + "mark": null, "x": 644, "y0": 644, "y1": 1288, @@ -14574,6 +16311,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 645, + "mark": null, "x": 645, "y0": 645, "y1": 1290, @@ -14582,6 +16320,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 646, + "mark": null, "x": 646, "y0": 646, "y1": 1292, @@ -14590,6 +16329,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 647, + "mark": null, "x": 647, "y0": 647, "y1": 1294, @@ -14598,6 +16338,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 648, + "mark": null, "x": 648, "y0": 648, "y1": 1296, @@ -14606,6 +16347,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 649, + "mark": null, "x": 649, "y0": 649, "y1": 1298, @@ -14614,6 +16356,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 650, + "mark": null, "x": 650, "y0": 650, "y1": 1300, @@ -14622,6 +16365,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 651, + "mark": null, "x": 651, "y0": 651, "y1": 1302, @@ -14630,6 +16374,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 652, + "mark": null, "x": 652, "y0": 652, "y1": 1304, @@ -14638,6 +16383,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 653, + "mark": null, "x": 653, "y0": 653, "y1": 1306, @@ -14646,6 +16392,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 654, + "mark": null, "x": 654, "y0": 654, "y1": 1308, @@ -14654,6 +16401,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 655, + "mark": null, "x": 655, "y0": 655, "y1": 1310, @@ -14662,6 +16410,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 656, + "mark": null, "x": 656, "y0": 656, "y1": 1312, @@ -14670,6 +16419,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 657, + "mark": null, "x": 657, "y0": 657, "y1": 1314, @@ -14678,6 +16428,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 658, + "mark": null, "x": 658, "y0": 658, "y1": 1316, @@ -14686,6 +16437,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 659, + "mark": null, "x": 659, "y0": 659, "y1": 1318, @@ -14694,6 +16446,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 660, + "mark": null, "x": 660, "y0": 660, "y1": 1320, @@ -14702,6 +16455,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 661, + "mark": null, "x": 661, "y0": 661, "y1": 1322, @@ -14710,6 +16464,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 662, + "mark": null, "x": 662, "y0": 662, "y1": 1324, @@ -14718,6 +16473,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 663, + "mark": null, "x": 663, "y0": 663, "y1": 1326, @@ -14726,6 +16482,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 664, + "mark": null, "x": 664, "y0": 664, "y1": 1328, @@ -14734,6 +16491,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 665, + "mark": null, "x": 665, "y0": 665, "y1": 1330, @@ -14742,6 +16500,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 666, + "mark": null, "x": 666, "y0": 666, "y1": 1332, @@ -14750,6 +16509,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 667, + "mark": null, "x": 667, "y0": 667, "y1": 1334, @@ -14758,6 +16518,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 668, + "mark": null, "x": 668, "y0": 668, "y1": 1336, @@ -14766,6 +16527,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 669, + "mark": null, "x": 669, "y0": 669, "y1": 1338, @@ -14774,6 +16536,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 670, + "mark": null, "x": 670, "y0": 670, "y1": 1340, @@ -14782,6 +16545,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 671, + "mark": null, "x": 671, "y0": 671, "y1": 1342, @@ -14790,6 +16554,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 672, + "mark": null, "x": 672, "y0": 672, "y1": 1344, @@ -14798,6 +16563,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 673, + "mark": null, "x": 673, "y0": 673, "y1": 1346, @@ -14806,6 +16572,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 674, + "mark": null, "x": 674, "y0": 674, "y1": 1348, @@ -14814,6 +16581,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 675, + "mark": null, "x": 675, "y0": 675, "y1": 1350, @@ -14822,6 +16590,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 676, + "mark": null, "x": 676, "y0": 676, "y1": 1352, @@ -14830,6 +16599,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 677, + "mark": null, "x": 677, "y0": 677, "y1": 1354, @@ -14838,6 +16608,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 678, + "mark": null, "x": 678, "y0": 678, "y1": 1356, @@ -14846,6 +16617,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 679, + "mark": null, "x": 679, "y0": 679, "y1": 1358, @@ -14854,6 +16626,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 680, + "mark": null, "x": 680, "y0": 680, "y1": 1360, @@ -14862,6 +16635,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 681, + "mark": null, "x": 681, "y0": 681, "y1": 1362, @@ -14870,6 +16644,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 682, + "mark": null, "x": 682, "y0": 682, "y1": 1364, @@ -14878,6 +16653,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 683, + "mark": null, "x": 683, "y0": 683, "y1": 1366, @@ -14886,6 +16662,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 684, + "mark": null, "x": 684, "y0": 684, "y1": 1368, @@ -14894,6 +16671,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 685, + "mark": null, "x": 685, "y0": 685, "y1": 1370, @@ -14902,6 +16680,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 686, + "mark": null, "x": 686, "y0": 686, "y1": 1372, @@ -14910,6 +16689,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 687, + "mark": null, "x": 687, "y0": 687, "y1": 1374, @@ -14918,6 +16698,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 688, + "mark": null, "x": 688, "y0": 688, "y1": 1376, @@ -14926,6 +16707,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 689, + "mark": null, "x": 689, "y0": 689, "y1": 1378, @@ -14934,6 +16716,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 690, + "mark": null, "x": 690, "y0": 690, "y1": 1380, @@ -14942,6 +16725,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 691, + "mark": null, "x": 691, "y0": 691, "y1": 1382, @@ -14950,6 +16734,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 692, + "mark": null, "x": 692, "y0": 692, "y1": 1384, @@ -14958,6 +16743,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 693, + "mark": null, "x": 693, "y0": 693, "y1": 1386, @@ -14966,6 +16752,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 694, + "mark": null, "x": 694, "y0": 694, "y1": 1388, @@ -14974,6 +16761,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 695, + "mark": null, "x": 695, "y0": 695, "y1": 1390, @@ -14982,6 +16770,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 696, + "mark": null, "x": 696, "y0": 696, "y1": 1392, @@ -14990,6 +16779,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 697, + "mark": null, "x": 697, "y0": 697, "y1": 1394, @@ -14998,6 +16788,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 698, + "mark": null, "x": 698, "y0": 698, "y1": 1396, @@ -15006,6 +16797,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 699, + "mark": null, "x": 699, "y0": 699, "y1": 1398, @@ -15014,6 +16806,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 700, + "mark": null, "x": 700, "y0": 700, "y1": 1400, @@ -15022,6 +16815,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 701, + "mark": null, "x": 701, "y0": 701, "y1": 1402, @@ -15030,6 +16824,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 702, + "mark": null, "x": 702, "y0": 702, "y1": 1404, @@ -15038,6 +16833,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 703, + "mark": null, "x": 703, "y0": 703, "y1": 1406, @@ -15046,6 +16842,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 704, + "mark": null, "x": 704, "y0": 704, "y1": 1408, @@ -15054,6 +16851,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 705, + "mark": null, "x": 705, "y0": 705, "y1": 1410, @@ -15062,6 +16860,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 706, + "mark": null, "x": 706, "y0": 706, "y1": 1412, @@ -15070,6 +16869,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 707, + "mark": null, "x": 707, "y0": 707, "y1": 1414, @@ -15078,6 +16878,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 708, + "mark": null, "x": 708, "y0": 708, "y1": 1416, @@ -15086,6 +16887,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 709, + "mark": null, "x": 709, "y0": 709, "y1": 1418, @@ -15094,6 +16896,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 710, + "mark": null, "x": 710, "y0": 710, "y1": 1420, @@ -15102,6 +16905,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 711, + "mark": null, "x": 711, "y0": 711, "y1": 1422, @@ -15110,6 +16914,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 712, + "mark": null, "x": 712, "y0": 712, "y1": 1424, @@ -15118,6 +16923,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 713, + "mark": null, "x": 713, "y0": 713, "y1": 1426, @@ -15126,6 +16932,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 714, + "mark": null, "x": 714, "y0": 714, "y1": 1428, @@ -15134,6 +16941,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 715, + "mark": null, "x": 715, "y0": 715, "y1": 1430, @@ -15142,6 +16950,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 716, + "mark": null, "x": 716, "y0": 716, "y1": 1432, @@ -15150,6 +16959,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 717, + "mark": null, "x": 717, "y0": 717, "y1": 1434, @@ -15158,6 +16968,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 718, + "mark": null, "x": 718, "y0": 718, "y1": 1436, @@ -15166,6 +16977,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 719, + "mark": null, "x": 719, "y0": 719, "y1": 1438, @@ -15174,6 +16986,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 720, + "mark": null, "x": 720, "y0": 720, "y1": 1440, @@ -15182,6 +16995,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 721, + "mark": null, "x": 721, "y0": 721, "y1": 1442, @@ -15190,6 +17004,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 722, + "mark": null, "x": 722, "y0": 722, "y1": 1444, @@ -15198,6 +17013,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 723, + "mark": null, "x": 723, "y0": 723, "y1": 1446, @@ -15206,6 +17022,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 724, + "mark": null, "x": 724, "y0": 724, "y1": 1448, @@ -15214,6 +17031,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 725, + "mark": null, "x": 725, "y0": 725, "y1": 1450, @@ -15222,6 +17040,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 726, + "mark": null, "x": 726, "y0": 726, "y1": 1452, @@ -15230,6 +17049,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 727, + "mark": null, "x": 727, "y0": 727, "y1": 1454, @@ -15238,6 +17058,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 728, + "mark": null, "x": 728, "y0": 728, "y1": 1456, @@ -15246,6 +17067,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 729, + "mark": null, "x": 729, "y0": 729, "y1": 1458, @@ -15254,6 +17076,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 730, + "mark": null, "x": 730, "y0": 730, "y1": 1460, @@ -15262,6 +17085,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 731, + "mark": null, "x": 731, "y0": 731, "y1": 1462, @@ -15270,6 +17094,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 732, + "mark": null, "x": 732, "y0": 732, "y1": 1464, @@ -15278,6 +17103,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 733, + "mark": null, "x": 733, "y0": 733, "y1": 1466, @@ -15286,6 +17112,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 734, + "mark": null, "x": 734, "y0": 734, "y1": 1468, @@ -15294,6 +17121,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 735, + "mark": null, "x": 735, "y0": 735, "y1": 1470, @@ -15302,6 +17130,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 736, + "mark": null, "x": 736, "y0": 736, "y1": 1472, @@ -15310,6 +17139,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 737, + "mark": null, "x": 737, "y0": 737, "y1": 1474, @@ -15318,6 +17148,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 738, + "mark": null, "x": 738, "y0": 738, "y1": 1476, @@ -15326,6 +17157,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 739, + "mark": null, "x": 739, "y0": 739, "y1": 1478, @@ -15334,6 +17166,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 740, + "mark": null, "x": 740, "y0": 740, "y1": 1480, @@ -15342,6 +17175,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 741, + "mark": null, "x": 741, "y0": 741, "y1": 1482, @@ -15350,6 +17184,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 742, + "mark": null, "x": 742, "y0": 742, "y1": 1484, @@ -15358,6 +17193,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 743, + "mark": null, "x": 743, "y0": 743, "y1": 1486, @@ -15366,6 +17202,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 744, + "mark": null, "x": 744, "y0": 744, "y1": 1488, @@ -15374,6 +17211,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 745, + "mark": null, "x": 745, "y0": 745, "y1": 1490, @@ -15382,6 +17220,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 746, + "mark": null, "x": 746, "y0": 746, "y1": 1492, @@ -15390,6 +17229,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 747, + "mark": null, "x": 747, "y0": 747, "y1": 1494, @@ -15398,6 +17238,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 748, + "mark": null, "x": 748, "y0": 748, "y1": 1496, @@ -15406,6 +17247,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 749, + "mark": null, "x": 749, "y0": 749, "y1": 1498, @@ -15414,6 +17256,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 750, + "mark": null, "x": 750, "y0": 750, "y1": 1500, @@ -15422,6 +17265,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 751, + "mark": null, "x": 751, "y0": 751, "y1": 1502, @@ -15430,6 +17274,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 752, + "mark": null, "x": 752, "y0": 752, "y1": 1504, @@ -15438,6 +17283,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 753, + "mark": null, "x": 753, "y0": 753, "y1": 1506, @@ -15446,6 +17292,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 754, + "mark": null, "x": 754, "y0": 754, "y1": 1508, @@ -15454,6 +17301,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 755, + "mark": null, "x": 755, "y0": 755, "y1": 1510, @@ -15462,6 +17310,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 756, + "mark": null, "x": 756, "y0": 756, "y1": 1512, @@ -15470,6 +17319,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 757, + "mark": null, "x": 757, "y0": 757, "y1": 1514, @@ -15478,6 +17328,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 758, + "mark": null, "x": 758, "y0": 758, "y1": 1516, @@ -15486,6 +17337,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 759, + "mark": null, "x": 759, "y0": 759, "y1": 1518, @@ -15494,6 +17346,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 760, + "mark": null, "x": 760, "y0": 760, "y1": 1520, @@ -15502,6 +17355,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 761, + "mark": null, "x": 761, "y0": 761, "y1": 1522, @@ -15510,6 +17364,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 762, + "mark": null, "x": 762, "y0": 762, "y1": 1524, @@ -15518,6 +17373,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 763, + "mark": null, "x": 763, "y0": 763, "y1": 1526, @@ -15526,6 +17382,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 764, + "mark": null, "x": 764, "y0": 764, "y1": 1528, @@ -15534,6 +17391,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 765, + "mark": null, "x": 765, "y0": 765, "y1": 1530, @@ -15542,6 +17400,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 766, + "mark": null, "x": 766, "y0": 766, "y1": 1532, @@ -15550,6 +17409,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 767, + "mark": null, "x": 767, "y0": 767, "y1": 1534, @@ -15558,6 +17418,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 768, + "mark": null, "x": 768, "y0": 768, "y1": 1536, @@ -15566,6 +17427,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 769, + "mark": null, "x": 769, "y0": 769, "y1": 1538, @@ -15574,6 +17436,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 770, + "mark": null, "x": 770, "y0": 770, "y1": 1540, @@ -15582,6 +17445,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 771, + "mark": null, "x": 771, "y0": 771, "y1": 1542, @@ -15590,6 +17454,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 772, + "mark": null, "x": 772, "y0": 772, "y1": 1544, @@ -15598,6 +17463,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 773, + "mark": null, "x": 773, "y0": 773, "y1": 1546, @@ -15606,6 +17472,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 774, + "mark": null, "x": 774, "y0": 774, "y1": 1548, @@ -15614,6 +17481,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 775, + "mark": null, "x": 775, "y0": 775, "y1": 1550, @@ -15622,6 +17490,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 776, + "mark": null, "x": 776, "y0": 776, "y1": 1552, @@ -15630,6 +17499,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 777, + "mark": null, "x": 777, "y0": 777, "y1": 1554, @@ -15638,6 +17508,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 778, + "mark": null, "x": 778, "y0": 778, "y1": 1556, @@ -15646,6 +17517,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 779, + "mark": null, "x": 779, "y0": 779, "y1": 1558, @@ -15654,6 +17526,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 780, + "mark": null, "x": 780, "y0": 780, "y1": 1560, @@ -15662,6 +17535,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 781, + "mark": null, "x": 781, "y0": 781, "y1": 1562, @@ -15670,6 +17544,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 782, + "mark": null, "x": 782, "y0": 782, "y1": 1564, @@ -15678,6 +17553,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 783, + "mark": null, "x": 783, "y0": 783, "y1": 1566, @@ -15686,6 +17562,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 784, + "mark": null, "x": 784, "y0": 784, "y1": 1568, @@ -15694,6 +17571,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 785, + "mark": null, "x": 785, "y0": 785, "y1": 1570, @@ -15702,6 +17580,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 786, + "mark": null, "x": 786, "y0": 786, "y1": 1572, @@ -15710,6 +17589,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 787, + "mark": null, "x": 787, "y0": 787, "y1": 1574, @@ -15718,6 +17598,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 788, + "mark": null, "x": 788, "y0": 788, "y1": 1576, @@ -15726,6 +17607,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 789, + "mark": null, "x": 789, "y0": 789, "y1": 1578, @@ -15734,6 +17616,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 790, + "mark": null, "x": 790, "y0": 790, "y1": 1580, @@ -15742,6 +17625,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 791, + "mark": null, "x": 791, "y0": 791, "y1": 1582, @@ -15750,6 +17634,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 792, + "mark": null, "x": 792, "y0": 792, "y1": 1584, @@ -15758,6 +17643,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 793, + "mark": null, "x": 793, "y0": 793, "y1": 1586, @@ -15766,6 +17652,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 794, + "mark": null, "x": 794, "y0": 794, "y1": 1588, @@ -15774,6 +17661,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 795, + "mark": null, "x": 795, "y0": 795, "y1": 1590, @@ -15782,6 +17670,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 796, + "mark": null, "x": 796, "y0": 796, "y1": 1592, @@ -15790,6 +17679,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 797, + "mark": null, "x": 797, "y0": 797, "y1": 1594, @@ -15798,6 +17688,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 798, + "mark": null, "x": 798, "y0": 798, "y1": 1596, @@ -15806,6 +17697,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 799, + "mark": null, "x": 799, "y0": 799, "y1": 1598, @@ -15814,6 +17706,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 800, + "mark": null, "x": 800, "y0": 800, "y1": 1600, @@ -15822,6 +17715,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 801, + "mark": null, "x": 801, "y0": 801, "y1": 1602, @@ -15830,6 +17724,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 802, + "mark": null, "x": 802, "y0": 802, "y1": 1604, @@ -15838,6 +17733,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 803, + "mark": null, "x": 803, "y0": 803, "y1": 1606, @@ -15846,6 +17742,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 804, + "mark": null, "x": 804, "y0": 804, "y1": 1608, @@ -15854,6 +17751,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 805, + "mark": null, "x": 805, "y0": 805, "y1": 1610, @@ -15862,6 +17760,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 806, + "mark": null, "x": 806, "y0": 806, "y1": 1612, @@ -15870,6 +17769,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 807, + "mark": null, "x": 807, "y0": 807, "y1": 1614, @@ -15878,6 +17778,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 808, + "mark": null, "x": 808, "y0": 808, "y1": 1616, @@ -15886,6 +17787,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 809, + "mark": null, "x": 809, "y0": 809, "y1": 1618, @@ -15894,6 +17796,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 810, + "mark": null, "x": 810, "y0": 810, "y1": 1620, @@ -15902,6 +17805,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 811, + "mark": null, "x": 811, "y0": 811, "y1": 1622, @@ -15910,6 +17814,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 812, + "mark": null, "x": 812, "y0": 812, "y1": 1624, @@ -15918,6 +17823,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 813, + "mark": null, "x": 813, "y0": 813, "y1": 1626, @@ -15926,6 +17832,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 814, + "mark": null, "x": 814, "y0": 814, "y1": 1628, @@ -15934,6 +17841,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 815, + "mark": null, "x": 815, "y0": 815, "y1": 1630, @@ -15942,6 +17850,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 816, + "mark": null, "x": 816, "y0": 816, "y1": 1632, @@ -15950,6 +17859,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 817, + "mark": null, "x": 817, "y0": 817, "y1": 1634, @@ -15958,6 +17868,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 818, + "mark": null, "x": 818, "y0": 818, "y1": 1636, @@ -15966,6 +17877,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 819, + "mark": null, "x": 819, "y0": 819, "y1": 1638, @@ -15974,6 +17886,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 820, + "mark": null, "x": 820, "y0": 820, "y1": 1640, @@ -15982,6 +17895,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 821, + "mark": null, "x": 821, "y0": 821, "y1": 1642, @@ -15990,6 +17904,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 822, + "mark": null, "x": 822, "y0": 822, "y1": 1644, @@ -15998,6 +17913,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 823, + "mark": null, "x": 823, "y0": 823, "y1": 1646, @@ -16006,6 +17922,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 824, + "mark": null, "x": 824, "y0": 824, "y1": 1648, @@ -16014,6 +17931,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 825, + "mark": null, "x": 825, "y0": 825, "y1": 1650, @@ -16022,6 +17940,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 826, + "mark": null, "x": 826, "y0": 826, "y1": 1652, @@ -16030,6 +17949,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 827, + "mark": null, "x": 827, "y0": 827, "y1": 1654, @@ -16038,6 +17958,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 828, + "mark": null, "x": 828, "y0": 828, "y1": 1656, @@ -16046,6 +17967,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 829, + "mark": null, "x": 829, "y0": 829, "y1": 1658, @@ -16054,6 +17976,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 830, + "mark": null, "x": 830, "y0": 830, "y1": 1660, @@ -16062,6 +17985,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 831, + "mark": null, "x": 831, "y0": 831, "y1": 1662, @@ -16070,6 +17994,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 832, + "mark": null, "x": 832, "y0": 832, "y1": 1664, @@ -16078,6 +18003,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 833, + "mark": null, "x": 833, "y0": 833, "y1": 1666, @@ -16086,6 +18012,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 834, + "mark": null, "x": 834, "y0": 834, "y1": 1668, @@ -16094,6 +18021,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 835, + "mark": null, "x": 835, "y0": 835, "y1": 1670, @@ -16102,6 +18030,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 836, + "mark": null, "x": 836, "y0": 836, "y1": 1672, @@ -16110,6 +18039,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 837, + "mark": null, "x": 837, "y0": 837, "y1": 1674, @@ -16118,6 +18048,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 838, + "mark": null, "x": 838, "y0": 838, "y1": 1676, @@ -16126,6 +18057,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 839, + "mark": null, "x": 839, "y0": 839, "y1": 1678, @@ -16134,6 +18066,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 840, + "mark": null, "x": 840, "y0": 840, "y1": 1680, @@ -16142,6 +18075,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 841, + "mark": null, "x": 841, "y0": 841, "y1": 1682, @@ -16150,6 +18084,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 842, + "mark": null, "x": 842, "y0": 842, "y1": 1684, @@ -16158,6 +18093,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 843, + "mark": null, "x": 843, "y0": 843, "y1": 1686, @@ -16166,6 +18102,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 844, + "mark": null, "x": 844, "y0": 844, "y1": 1688, @@ -16174,6 +18111,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 845, + "mark": null, "x": 845, "y0": 845, "y1": 1690, @@ -16182,6 +18120,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 846, + "mark": null, "x": 846, "y0": 846, "y1": 1692, @@ -16190,6 +18129,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 847, + "mark": null, "x": 847, "y0": 847, "y1": 1694, @@ -16198,6 +18138,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 848, + "mark": null, "x": 848, "y0": 848, "y1": 1696, @@ -16206,6 +18147,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 849, + "mark": null, "x": 849, "y0": 849, "y1": 1698, @@ -16214,6 +18156,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 850, + "mark": null, "x": 850, "y0": 850, "y1": 1700, @@ -16222,6 +18165,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 851, + "mark": null, "x": 851, "y0": 851, "y1": 1702, @@ -16230,6 +18174,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 852, + "mark": null, "x": 852, "y0": 852, "y1": 1704, @@ -16238,6 +18183,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 853, + "mark": null, "x": 853, "y0": 853, "y1": 1706, @@ -16246,6 +18192,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 854, + "mark": null, "x": 854, "y0": 854, "y1": 1708, @@ -16254,6 +18201,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 855, + "mark": null, "x": 855, "y0": 855, "y1": 1710, @@ -16262,6 +18210,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 856, + "mark": null, "x": 856, "y0": 856, "y1": 1712, @@ -16270,6 +18219,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 857, + "mark": null, "x": 857, "y0": 857, "y1": 1714, @@ -16278,6 +18228,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 858, + "mark": null, "x": 858, "y0": 858, "y1": 1716, @@ -16286,6 +18237,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 859, + "mark": null, "x": 859, "y0": 859, "y1": 1718, @@ -16294,6 +18246,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 860, + "mark": null, "x": 860, "y0": 860, "y1": 1720, @@ -16302,6 +18255,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 861, + "mark": null, "x": 861, "y0": 861, "y1": 1722, @@ -16310,6 +18264,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 862, + "mark": null, "x": 862, "y0": 862, "y1": 1724, @@ -16318,6 +18273,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 863, + "mark": null, "x": 863, "y0": 863, "y1": 1726, @@ -16326,6 +18282,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 864, + "mark": null, "x": 864, "y0": 864, "y1": 1728, @@ -16334,6 +18291,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 865, + "mark": null, "x": 865, "y0": 865, "y1": 1730, @@ -16342,6 +18300,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 866, + "mark": null, "x": 866, "y0": 866, "y1": 1732, @@ -16350,6 +18309,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 867, + "mark": null, "x": 867, "y0": 867, "y1": 1734, @@ -16358,6 +18318,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 868, + "mark": null, "x": 868, "y0": 868, "y1": 1736, @@ -16366,6 +18327,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 869, + "mark": null, "x": 869, "y0": 869, "y1": 1738, @@ -16374,6 +18336,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 870, + "mark": null, "x": 870, "y0": 870, "y1": 1740, @@ -16382,6 +18345,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 871, + "mark": null, "x": 871, "y0": 871, "y1": 1742, @@ -16390,6 +18354,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 872, + "mark": null, "x": 872, "y0": 872, "y1": 1744, @@ -16398,6 +18363,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 873, + "mark": null, "x": 873, "y0": 873, "y1": 1746, @@ -16406,6 +18372,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 874, + "mark": null, "x": 874, "y0": 874, "y1": 1748, @@ -16414,6 +18381,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 875, + "mark": null, "x": 875, "y0": 875, "y1": 1750, @@ -16422,6 +18390,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 876, + "mark": null, "x": 876, "y0": 876, "y1": 1752, @@ -16430,6 +18399,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 877, + "mark": null, "x": 877, "y0": 877, "y1": 1754, @@ -16438,6 +18408,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 878, + "mark": null, "x": 878, "y0": 878, "y1": 1756, @@ -16446,6 +18417,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 879, + "mark": null, "x": 879, "y0": 879, "y1": 1758, @@ -16454,6 +18426,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 880, + "mark": null, "x": 880, "y0": 880, "y1": 1760, @@ -16462,6 +18435,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 881, + "mark": null, "x": 881, "y0": 881, "y1": 1762, @@ -16470,6 +18444,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 882, + "mark": null, "x": 882, "y0": 882, "y1": 1764, @@ -16478,6 +18453,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 883, + "mark": null, "x": 883, "y0": 883, "y1": 1766, @@ -16486,6 +18462,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 884, + "mark": null, "x": 884, "y0": 884, "y1": 1768, @@ -16494,6 +18471,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 885, + "mark": null, "x": 885, "y0": 885, "y1": 1770, @@ -16502,6 +18480,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 886, + "mark": null, "x": 886, "y0": 886, "y1": 1772, @@ -16510,6 +18489,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 887, + "mark": null, "x": 887, "y0": 887, "y1": 1774, @@ -16518,6 +18498,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 888, + "mark": null, "x": 888, "y0": 888, "y1": 1776, @@ -16526,6 +18507,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 889, + "mark": null, "x": 889, "y0": 889, "y1": 1778, @@ -16534,6 +18516,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 890, + "mark": null, "x": 890, "y0": 890, "y1": 1780, @@ -16542,6 +18525,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 891, + "mark": null, "x": 891, "y0": 891, "y1": 1782, @@ -16550,6 +18534,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 892, + "mark": null, "x": 892, "y0": 892, "y1": 1784, @@ -16558,6 +18543,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 893, + "mark": null, "x": 893, "y0": 893, "y1": 1786, @@ -16566,6 +18552,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 894, + "mark": null, "x": 894, "y0": 894, "y1": 1788, @@ -16574,6 +18561,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 895, + "mark": null, "x": 895, "y0": 895, "y1": 1790, @@ -16582,6 +18570,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 896, + "mark": null, "x": 896, "y0": 896, "y1": 1792, @@ -16590,6 +18579,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 897, + "mark": null, "x": 897, "y0": 897, "y1": 1794, @@ -16598,6 +18588,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 898, + "mark": null, "x": 898, "y0": 898, "y1": 1796, @@ -16606,6 +18597,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 899, + "mark": null, "x": 899, "y0": 899, "y1": 1798, @@ -16614,6 +18606,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 900, + "mark": null, "x": 900, "y0": 900, "y1": 1800, @@ -16622,6 +18615,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 901, + "mark": null, "x": 901, "y0": 901, "y1": 1802, @@ -16630,6 +18624,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 902, + "mark": null, "x": 902, "y0": 902, "y1": 1804, @@ -16638,6 +18633,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 903, + "mark": null, "x": 903, "y0": 903, "y1": 1806, @@ -16646,6 +18642,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 904, + "mark": null, "x": 904, "y0": 904, "y1": 1808, @@ -16654,6 +18651,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 905, + "mark": null, "x": 905, "y0": 905, "y1": 1810, @@ -16662,6 +18660,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 906, + "mark": null, "x": 906, "y0": 906, "y1": 1812, @@ -16670,6 +18669,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 907, + "mark": null, "x": 907, "y0": 907, "y1": 1814, @@ -16678,6 +18678,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 908, + "mark": null, "x": 908, "y0": 908, "y1": 1816, @@ -16686,6 +18687,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 909, + "mark": null, "x": 909, "y0": 909, "y1": 1818, @@ -16694,6 +18696,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 910, + "mark": null, "x": 910, "y0": 910, "y1": 1820, @@ -16702,6 +18705,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 911, + "mark": null, "x": 911, "y0": 911, "y1": 1822, @@ -16710,6 +18714,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 912, + "mark": null, "x": 912, "y0": 912, "y1": 1824, @@ -16718,6 +18723,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 913, + "mark": null, "x": 913, "y0": 913, "y1": 1826, @@ -16726,6 +18732,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 914, + "mark": null, "x": 914, "y0": 914, "y1": 1828, @@ -16734,6 +18741,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 915, + "mark": null, "x": 915, "y0": 915, "y1": 1830, @@ -16742,6 +18750,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 916, + "mark": null, "x": 916, "y0": 916, "y1": 1832, @@ -16750,6 +18759,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 917, + "mark": null, "x": 917, "y0": 917, "y1": 1834, @@ -16758,6 +18768,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 918, + "mark": null, "x": 918, "y0": 918, "y1": 1836, @@ -16766,6 +18777,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 919, + "mark": null, "x": 919, "y0": 919, "y1": 1838, @@ -16774,6 +18786,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 920, + "mark": null, "x": 920, "y0": 920, "y1": 1840, @@ -16782,6 +18795,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 921, + "mark": null, "x": 921, "y0": 921, "y1": 1842, @@ -16790,6 +18804,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 922, + "mark": null, "x": 922, "y0": 922, "y1": 1844, @@ -16798,6 +18813,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 923, + "mark": null, "x": 923, "y0": 923, "y1": 1846, @@ -16806,6 +18822,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 924, + "mark": null, "x": 924, "y0": 924, "y1": 1848, @@ -16814,6 +18831,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 925, + "mark": null, "x": 925, "y0": 925, "y1": 1850, @@ -16822,6 +18840,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 926, + "mark": null, "x": 926, "y0": 926, "y1": 1852, @@ -16830,6 +18849,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 927, + "mark": null, "x": 927, "y0": 927, "y1": 1854, @@ -16838,6 +18858,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 928, + "mark": null, "x": 928, "y0": 928, "y1": 1856, @@ -16846,6 +18867,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 929, + "mark": null, "x": 929, "y0": 929, "y1": 1858, @@ -16854,6 +18876,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 930, + "mark": null, "x": 930, "y0": 930, "y1": 1860, @@ -16862,6 +18885,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 931, + "mark": null, "x": 931, "y0": 931, "y1": 1862, @@ -16870,6 +18894,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 932, + "mark": null, "x": 932, "y0": 932, "y1": 1864, @@ -16878,6 +18903,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 933, + "mark": null, "x": 933, "y0": 933, "y1": 1866, @@ -16886,6 +18912,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 934, + "mark": null, "x": 934, "y0": 934, "y1": 1868, @@ -16894,6 +18921,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 935, + "mark": null, "x": 935, "y0": 935, "y1": 1870, @@ -16902,6 +18930,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 936, + "mark": null, "x": 936, "y0": 936, "y1": 1872, @@ -16910,6 +18939,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 937, + "mark": null, "x": 937, "y0": 937, "y1": 1874, @@ -16918,6 +18948,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 938, + "mark": null, "x": 938, "y0": 938, "y1": 1876, @@ -16926,6 +18957,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 939, + "mark": null, "x": 939, "y0": 939, "y1": 1878, @@ -16934,6 +18966,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 940, + "mark": null, "x": 940, "y0": 940, "y1": 1880, @@ -16942,6 +18975,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 941, + "mark": null, "x": 941, "y0": 941, "y1": 1882, @@ -16950,6 +18984,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 942, + "mark": null, "x": 942, "y0": 942, "y1": 1884, @@ -16958,6 +18993,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 943, + "mark": null, "x": 943, "y0": 943, "y1": 1886, @@ -16966,6 +19002,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 944, + "mark": null, "x": 944, "y0": 944, "y1": 1888, @@ -16974,6 +19011,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 945, + "mark": null, "x": 945, "y0": 945, "y1": 1890, @@ -16982,6 +19020,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 946, + "mark": null, "x": 946, "y0": 946, "y1": 1892, @@ -16990,6 +19029,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 947, + "mark": null, "x": 947, "y0": 947, "y1": 1894, @@ -16998,6 +19038,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 948, + "mark": null, "x": 948, "y0": 948, "y1": 1896, @@ -17006,6 +19047,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 949, + "mark": null, "x": 949, "y0": 949, "y1": 1898, @@ -17014,6 +19056,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 950, + "mark": null, "x": 950, "y0": 950, "y1": 1900, @@ -17022,6 +19065,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 951, + "mark": null, "x": 951, "y0": 951, "y1": 1902, @@ -17030,6 +19074,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 952, + "mark": null, "x": 952, "y0": 952, "y1": 1904, @@ -17038,6 +19083,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 953, + "mark": null, "x": 953, "y0": 953, "y1": 1906, @@ -17046,6 +19092,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 954, + "mark": null, "x": 954, "y0": 954, "y1": 1908, @@ -17054,6 +19101,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 955, + "mark": null, "x": 955, "y0": 955, "y1": 1910, @@ -17062,6 +19110,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 956, + "mark": null, "x": 956, "y0": 956, "y1": 1912, @@ -17070,6 +19119,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 957, + "mark": null, "x": 957, "y0": 957, "y1": 1914, @@ -17078,6 +19128,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 958, + "mark": null, "x": 958, "y0": 958, "y1": 1916, @@ -17086,6 +19137,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 959, + "mark": null, "x": 959, "y0": 959, "y1": 1918, @@ -17094,6 +19146,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 960, + "mark": null, "x": 960, "y0": 960, "y1": 1920, @@ -17102,6 +19155,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 961, + "mark": null, "x": 961, "y0": 961, "y1": 1922, @@ -17110,6 +19164,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 962, + "mark": null, "x": 962, "y0": 962, "y1": 1924, @@ -17118,6 +19173,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 963, + "mark": null, "x": 963, "y0": 963, "y1": 1926, @@ -17126,6 +19182,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 964, + "mark": null, "x": 964, "y0": 964, "y1": 1928, @@ -17134,6 +19191,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 965, + "mark": null, "x": 965, "y0": 965, "y1": 1930, @@ -17142,6 +19200,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 966, + "mark": null, "x": 966, "y0": 966, "y1": 1932, @@ -17150,6 +19209,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 967, + "mark": null, "x": 967, "y0": 967, "y1": 1934, @@ -17158,6 +19218,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 968, + "mark": null, "x": 968, "y0": 968, "y1": 1936, @@ -17166,6 +19227,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 969, + "mark": null, "x": 969, "y0": 969, "y1": 1938, @@ -17174,6 +19236,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 970, + "mark": null, "x": 970, "y0": 970, "y1": 1940, @@ -17182,6 +19245,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 971, + "mark": null, "x": 971, "y0": 971, "y1": 1942, @@ -17190,6 +19254,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 972, + "mark": null, "x": 972, "y0": 972, "y1": 1944, @@ -17198,6 +19263,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 973, + "mark": null, "x": 973, "y0": 973, "y1": 1946, @@ -17206,6 +19272,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 974, + "mark": null, "x": 974, "y0": 974, "y1": 1948, @@ -17214,6 +19281,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 975, + "mark": null, "x": 975, "y0": 975, "y1": 1950, @@ -17222,6 +19290,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 976, + "mark": null, "x": 976, "y0": 976, "y1": 1952, @@ -17230,6 +19299,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 977, + "mark": null, "x": 977, "y0": 977, "y1": 1954, @@ -17238,6 +19308,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 978, + "mark": null, "x": 978, "y0": 978, "y1": 1956, @@ -17246,6 +19317,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 979, + "mark": null, "x": 979, "y0": 979, "y1": 1958, @@ -17254,6 +19326,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 980, + "mark": null, "x": 980, "y0": 980, "y1": 1960, @@ -17262,6 +19335,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 981, + "mark": null, "x": 981, "y0": 981, "y1": 1962, @@ -17270,6 +19344,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 982, + "mark": null, "x": 982, "y0": 982, "y1": 1964, @@ -17278,6 +19353,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 983, + "mark": null, "x": 983, "y0": 983, "y1": 1966, @@ -17286,6 +19362,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 984, + "mark": null, "x": 984, "y0": 984, "y1": 1968, @@ -17294,6 +19371,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 985, + "mark": null, "x": 985, "y0": 985, "y1": 1970, @@ -17302,6 +19380,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 986, + "mark": null, "x": 986, "y0": 986, "y1": 1972, @@ -17310,6 +19389,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 987, + "mark": null, "x": 987, "y0": 987, "y1": 1974, @@ -17318,6 +19398,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 988, + "mark": null, "x": 988, "y0": 988, "y1": 1976, @@ -17326,6 +19407,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 989, + "mark": null, "x": 989, "y0": 989, "y1": 1978, @@ -17334,6 +19416,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 990, + "mark": null, "x": 990, "y0": 990, "y1": 1980, @@ -17342,6 +19425,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 991, + "mark": null, "x": 991, "y0": 991, "y1": 1982, @@ -17350,6 +19434,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 992, + "mark": null, "x": 992, "y0": 992, "y1": 1984, @@ -17358,6 +19443,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 993, + "mark": null, "x": 993, "y0": 993, "y1": 1986, @@ -17366,6 +19452,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 994, + "mark": null, "x": 994, "y0": 994, "y1": 1988, @@ -17374,6 +19461,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 995, + "mark": null, "x": 995, "y0": 995, "y1": 1990, @@ -17382,6 +19470,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 996, + "mark": null, "x": 996, "y0": 996, "y1": 1992, @@ -17390,6 +19479,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 997, + "mark": null, "x": 997, "y0": 997, "y1": 1994, @@ -17398,6 +19488,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 998, + "mark": null, "x": 998, "y0": 998, "y1": 1996, @@ -17406,6 +19497,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 999, + "mark": null, "x": 999, "y0": 999, "y1": 1998, @@ -17430,6 +19522,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": null, "y1": 1, @@ -17438,6 +19531,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": null, "y1": 2, @@ -17446,6 +19540,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 3, + "mark": null, "x": 3, "y0": null, "y1": 3, @@ -17454,6 +19549,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": null, "y1": 4, @@ -17473,6 +19569,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": 1, "y1": 2, @@ -17481,6 +19578,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": 2, "y1": 4, @@ -17489,6 +19587,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 3, + "mark": null, "x": 3, "y0": 3, "y1": 6, @@ -17497,6 +19596,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": 4, "y1": 8, @@ -17516,6 +19616,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": 2, "y1": 3, @@ -17524,6 +19625,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": 4, "y1": 6, @@ -17532,6 +19634,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 3, + "mark": null, "x": 3, "y0": 6, "y1": 9, @@ -17540,6 +19643,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": 8, "y1": 12, @@ -17559,6 +19663,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": 3, "y1": 4, @@ -17567,6 +19672,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": 6, "y1": 8, @@ -17575,6 +19681,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 3, + "mark": null, "x": 3, "y0": 9, "y1": 12, @@ -17583,6 +19690,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": 12, "y1": 16, @@ -17607,6 +19715,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": 1, "y1": 1, @@ -17615,6 +19724,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": 2, "y1": 2, @@ -17623,6 +19733,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 3, + "mark": null, "x": 3, "y0": 3, "y1": 3, @@ -17631,6 +19742,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": 4, "y1": 4, @@ -17650,6 +19762,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": 1, "y1": 2, @@ -17658,6 +19771,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": 2, "y1": 4, @@ -17666,6 +19780,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 3, + "mark": null, "x": 3, "y0": 3, "y1": 6, @@ -17674,6 +19789,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": 4, "y1": 8, @@ -17693,6 +19809,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": 2, "y1": 3, @@ -17701,6 +19818,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": 4, "y1": 6, @@ -17709,6 +19827,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 3, + "mark": null, "x": 3, "y0": 6, "y1": 9, @@ -17717,6 +19836,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": 8, "y1": 12, @@ -17736,6 +19856,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": 3, "y1": 4, @@ -17744,6 +19865,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": 6, "y1": 8, @@ -17752,6 +19874,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 3, + "mark": null, "x": 3, "y0": 9, "y1": 12, @@ -17760,6 +19883,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": 12, "y1": 16, @@ -17784,6 +19908,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": null, "y1": 1, @@ -17792,18 +19917,20 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": null, "y1": 2, }, Object { - "datum": undefined, + "datum": null, "filled": Object { "x": 3, "y1": 0, }, "initialY0": null, "initialY1": 0, + "mark": null, "x": 3, "y0": null, "y1": 0, @@ -17812,6 +19939,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": null, "y1": 4, @@ -17831,18 +19959,20 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 21, + "mark": null, "x": 1, "y0": 1, "y1": 22, }, Object { - "datum": undefined, + "datum": null, "filled": Object { "x": 2, "y1": 0, }, "initialY0": null, "initialY1": 0, + "mark": null, "x": 2, "y0": 2, "y1": 2, @@ -17851,18 +19981,20 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 23, + "mark": null, "x": 3, "y0": 0, "y1": 23, }, Object { - "datum": undefined, + "datum": null, "filled": Object { "x": 4, "y1": 0, }, "initialY0": null, "initialY1": 0, + "mark": null, "x": 4, "y0": 4, "y1": 4, @@ -17887,6 +20019,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": 1, "y1": 1, @@ -17895,18 +20028,20 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": 2, "y1": 2, }, Object { - "datum": undefined, + "datum": null, "filled": Object { "x": 3, "y1": 0, }, "initialY0": null, "initialY1": 0, + "mark": null, "x": 3, "y0": 0, "y1": 0, @@ -17915,6 +20050,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": 4, "y1": 4, @@ -17934,18 +20070,20 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 21, + "mark": null, "x": 1, "y0": 1, "y1": 22, }, Object { - "datum": undefined, + "datum": null, "filled": Object { "x": 2, "y1": 0, }, "initialY0": null, "initialY1": 0, + "mark": null, "x": 2, "y0": 2, "y1": 2, @@ -17954,18 +20092,20 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 23, + "mark": null, "x": 3, "y0": 0, "y1": 23, }, Object { - "datum": undefined, + "datum": null, "filled": Object { "x": 4, "y1": 0, }, "initialY0": null, "initialY1": 0, + "mark": null, "x": 4, "y0": 4, "y1": 4, @@ -17990,6 +20130,7 @@ Array [ "datum": undefined, "initialY0": 1, "initialY1": 3, + "mark": null, "x": 1, "y0": 1, "y1": 3, @@ -17998,18 +20139,20 @@ Array [ "datum": undefined, "initialY0": 2, "initialY1": 3, + "mark": null, "x": 2, "y0": 2, "y1": 3, }, Object { - "datum": undefined, + "datum": null, "filled": Object { "x": 3, "y1": 0, }, "initialY0": null, "initialY1": 0, + "mark": null, "x": 3, "y0": 0, "y1": 0, @@ -18018,6 +20161,7 @@ Array [ "datum": undefined, "initialY0": 3, "initialY1": 4, + "mark": null, "x": 4, "y0": 3, "y1": 4, @@ -18037,6 +20181,7 @@ Array [ "datum": undefined, "initialY0": 1, "initialY1": 2, + "mark": null, "x": 1, "y0": 4, "y1": 5, @@ -18045,6 +20190,7 @@ Array [ "datum": undefined, "initialY0": 1, "initialY1": 3, + "mark": null, "x": 2, "y0": 4, "y1": 6, @@ -18053,6 +20199,7 @@ Array [ "datum": undefined, "initialY0": 4, "initialY1": 23, + "mark": null, "x": 3, "y0": 4, "y1": 23, @@ -18061,6 +20208,7 @@ Array [ "datum": undefined, "initialY0": 1, "initialY1": 4, + "mark": null, "x": 4, "y0": 5, "y1": 8, @@ -18085,6 +20233,7 @@ Array [ "datum": undefined, "initialY0": 1, "initialY1": 3, + "mark": null, "x": 1, "y0": 1, "y1": 3, @@ -18093,18 +20242,20 @@ Array [ "datum": undefined, "initialY0": 2, "initialY1": 3, + "mark": null, "x": 2, "y0": 2, "y1": 3, }, Object { - "datum": undefined, + "datum": null, "filled": Object { "x": 3, "y1": 0, }, "initialY0": null, "initialY1": 0, + "mark": null, "x": 3, "y0": 0, "y1": 0, @@ -18113,6 +20264,7 @@ Array [ "datum": undefined, "initialY0": 3, "initialY1": 4, + "mark": null, "x": 4, "y0": 3, "y1": 4, @@ -18132,6 +20284,7 @@ Array [ "datum": undefined, "initialY0": 1, "initialY1": 2, + "mark": null, "x": 1, "y0": 4, "y1": 5, @@ -18140,6 +20293,7 @@ Array [ "datum": undefined, "initialY0": 1, "initialY1": 3, + "mark": null, "x": 2, "y0": 4, "y1": 6, @@ -18148,6 +20302,7 @@ Array [ "datum": undefined, "initialY0": 4, "initialY1": 23, + "mark": null, "x": 3, "y0": 4, "y1": 23, @@ -18156,6 +20311,7 @@ Array [ "datum": undefined, "initialY0": 1, "initialY1": 4, + "mark": null, "x": 4, "y0": 5, "y1": 8, @@ -18180,6 +20336,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 1, + "mark": null, "x": 1, "y0": null, "y1": 1, @@ -18188,18 +20345,20 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": null, "y1": 2, }, Object { - "datum": undefined, + "datum": null, "filled": Object { "x": 3, "y1": 0, }, "initialY0": null, "initialY1": 0, + "mark": null, "x": 3, "y0": null, "y1": 0, @@ -18208,6 +20367,7 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 4, + "mark": null, "x": 4, "y0": null, "y1": 4, @@ -18227,18 +20387,20 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 21, + "mark": null, "x": 1, "y0": 1, "y1": 22, }, Object { - "datum": undefined, + "datum": null, "filled": Object { "x": 2, "y1": 0, }, "initialY0": null, "initialY1": 0, + "mark": null, "x": 2, "y0": 2, "y1": 2, @@ -18247,18 +20409,20 @@ Array [ "datum": undefined, "initialY0": null, "initialY1": 23, + "mark": null, "x": 3, "y0": 0, "y1": 23, }, Object { - "datum": undefined, + "datum": null, "filled": Object { "x": 4, "y1": 0, }, "initialY0": null, "initialY1": 0, + "mark": null, "x": 4, "y0": 4, "y1": 4, @@ -18287,6 +20451,7 @@ Array [ "y1": 1, "y2": 4, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -18299,6 +20464,7 @@ Array [ "y1": 2, "y2": 1, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -18311,6 +20477,7 @@ Array [ "y1": 10, "y2": 5, }, + "mark": null, "x": 2, "y0": null, "y1": 10, @@ -18323,6 +20490,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 3, "y0": null, "y1": 7, @@ -18335,6 +20503,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 6, "y0": null, "y1": 7, @@ -18363,6 +20532,7 @@ Array [ "y1": 1, "y2": 4, }, + "mark": null, "x": 0, "y0": null, "y1": 4, @@ -18375,6 +20545,7 @@ Array [ "y1": 2, "y2": 1, }, + "mark": null, "x": 1, "y0": null, "y1": 1, @@ -18387,6 +20558,7 @@ Array [ "y1": 10, "y2": 5, }, + "mark": null, "x": 2, "y0": null, "y1": 5, @@ -18399,6 +20571,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 3, "y0": null, "y1": 3, @@ -18411,6 +20584,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 6, "y0": null, "y1": 3, @@ -18439,6 +20613,7 @@ Array [ "y1": 1, "y2": 4, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -18451,6 +20626,7 @@ Array [ "y1": 2, "y2": 1, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -18463,6 +20639,7 @@ Array [ "y1": 10, "y2": 5, }, + "mark": null, "x": 2, "y0": null, "y1": 10, @@ -18475,6 +20652,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 3, "y0": null, "y1": 7, @@ -18487,6 +20665,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 6, "y0": null, "y1": 7, @@ -18515,6 +20694,7 @@ Array [ "y1": 1, "y2": 4, }, + "mark": null, "x": 0, "y0": null, "y1": 4, @@ -18527,6 +20707,7 @@ Array [ "y1": 2, "y2": 1, }, + "mark": null, "x": 1, "y0": null, "y1": 1, @@ -18539,6 +20720,7 @@ Array [ "y1": 10, "y2": 5, }, + "mark": null, "x": 2, "y0": null, "y1": 5, @@ -18551,6 +20733,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 3, "y0": null, "y1": 3, @@ -18563,6 +20746,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": 6, "y0": null, "y1": 3, @@ -18591,6 +20775,7 @@ Array [ "y1": 3, "y2": 6, }, + "mark": null, "x": 0, "y0": null, "y1": 3, @@ -18603,6 +20788,7 @@ Array [ "y1": 2, "y2": 5, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -18615,6 +20801,7 @@ Array [ "y1": 3, "y2": 1, }, + "mark": null, "x": 2, "y0": null, "y1": 3, @@ -18627,6 +20814,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -18639,6 +20827,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 6, "y0": null, "y1": 6, @@ -18667,6 +20856,7 @@ Array [ "y1": 3, "y2": 6, }, + "mark": null, "x": 0, "y0": null, "y1": 6, @@ -18679,6 +20869,7 @@ Array [ "y1": 2, "y2": 5, }, + "mark": null, "x": 1, "y0": null, "y1": 5, @@ -18691,6 +20882,7 @@ Array [ "y1": 3, "y2": 1, }, + "mark": null, "x": 2, "y0": null, "y1": 1, @@ -18703,6 +20895,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 3, "y0": null, "y1": 4, @@ -18715,6 +20908,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 6, "y0": null, "y1": 4, @@ -18743,6 +20937,7 @@ Array [ "y1": 3, "y2": 6, }, + "mark": null, "x": 0, "y0": null, "y1": 3, @@ -18755,6 +20950,7 @@ Array [ "y1": 2, "y2": 5, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -18767,6 +20963,7 @@ Array [ "y1": 3, "y2": 1, }, + "mark": null, "x": 2, "y0": null, "y1": 3, @@ -18779,6 +20976,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -18791,6 +20989,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 6, "y0": null, "y1": 6, @@ -18819,6 +21018,7 @@ Array [ "y1": 3, "y2": 6, }, + "mark": null, "x": 0, "y0": null, "y1": 6, @@ -18831,6 +21031,7 @@ Array [ "y1": 2, "y2": 5, }, + "mark": null, "x": 1, "y0": null, "y1": 5, @@ -18843,6 +21044,7 @@ Array [ "y1": 3, "y2": 1, }, + "mark": null, "x": 2, "y0": null, "y1": 1, @@ -18855,6 +21057,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 3, "y0": null, "y1": 4, @@ -18867,6 +21070,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": 6, "y0": null, "y1": 4, @@ -18900,6 +21104,7 @@ Array [ "y1": 1, "y2": 4, }, + "mark": null, "x": "_all", "y0": null, "y1": 1, @@ -18912,6 +21117,7 @@ Array [ "y1": 1, "y2": 4, }, + "mark": null, "x": "_all", "y0": null, "y1": 1, @@ -18924,6 +21130,7 @@ Array [ "y1": 3, "y2": 6, }, + "mark": null, "x": "_all", "y0": null, "y1": 3, @@ -18936,6 +21143,7 @@ Array [ "y1": 3, "y2": 6, }, + "mark": null, "x": "_all", "y0": null, "y1": 3, @@ -18948,6 +21156,7 @@ Array [ "y1": 2, "y2": 1, }, + "mark": null, "x": "_all", "y0": null, "y1": 2, @@ -18960,6 +21169,7 @@ Array [ "y1": 2, "y2": 1, }, + "mark": null, "x": "_all", "y0": null, "y1": 2, @@ -18972,6 +21182,7 @@ Array [ "y1": 2, "y2": 5, }, + "mark": null, "x": "_all", "y0": null, "y1": 2, @@ -18984,6 +21195,7 @@ Array [ "y1": 2, "y2": 5, }, + "mark": null, "x": "_all", "y0": null, "y1": 2, @@ -18996,6 +21208,7 @@ Array [ "y1": 10, "y2": 5, }, + "mark": null, "x": "_all", "y0": null, "y1": 10, @@ -19008,6 +21221,7 @@ Array [ "y1": 10, "y2": 5, }, + "mark": null, "x": "_all", "y0": null, "y1": 10, @@ -19020,6 +21234,7 @@ Array [ "y1": 3, "y2": 1, }, + "mark": null, "x": "_all", "y0": null, "y1": 3, @@ -19032,6 +21247,7 @@ Array [ "y1": 3, "y2": 1, }, + "mark": null, "x": "_all", "y0": null, "y1": 3, @@ -19044,6 +21260,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": "_all", "y0": null, "y1": 7, @@ -19056,6 +21273,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": "_all", "y0": null, "y1": 7, @@ -19068,6 +21286,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": "_all", "y0": null, "y1": 6, @@ -19080,6 +21299,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": "_all", "y0": null, "y1": 6, @@ -19092,6 +21312,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": "_all", "y0": null, "y1": 7, @@ -19104,6 +21325,7 @@ Array [ "y1": 7, "y2": 3, }, + "mark": null, "x": "_all", "y0": null, "y1": 7, @@ -19116,6 +21338,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": "_all", "y0": null, "y1": 6, @@ -19128,6 +21351,7 @@ Array [ "y1": 6, "y2": 4, }, + "mark": null, "x": "_all", "y0": null, "y1": 6, @@ -19154,6 +21378,7 @@ Array [ 1, "a", ], + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -19164,6 +21389,7 @@ Array [ 1, "a", ], + "mark": null, "x": 1, "y0": null, "y1": 1, @@ -19174,6 +21400,7 @@ Array [ 1, "a", ], + "mark": null, "x": 2, "y0": null, "y1": 1, @@ -19198,6 +21425,7 @@ Array [ 1, "b", ], + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -19208,6 +21436,7 @@ Array [ 1, "b", ], + "mark": null, "x": 1, "y0": null, "y1": 1, @@ -19218,6 +21447,7 @@ Array [ 1, "b", ], + "mark": null, "x": 2, "y0": null, "y1": 1, @@ -19256,6 +21486,7 @@ Array [ }, "initialY0": null, "initialY1": 1, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -19268,6 +21499,7 @@ Array [ }, "initialY0": null, "initialY1": 2, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -19280,6 +21512,7 @@ Array [ }, "initialY0": null, "initialY1": 1, + "mark": null, "x": 2, "y0": null, "y1": 1, @@ -19292,6 +21525,7 @@ Array [ }, "initialY0": null, "initialY1": 6, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -19315,6 +21549,7 @@ Array [ }, "initialY0": null, "initialY1": 3, + "mark": null, "x": 0, "y0": 1, "y1": 4, @@ -19327,6 +21562,7 @@ Array [ }, "initialY0": null, "initialY1": 7, + "mark": null, "x": 1, "y0": 2, "y1": 9, @@ -19339,6 +21575,7 @@ Array [ }, "initialY0": null, "initialY1": 2, + "mark": null, "x": 2, "y0": 1, "y1": 3, @@ -19351,6 +21588,7 @@ Array [ }, "initialY0": null, "initialY1": 10, + "mark": null, "x": 3, "y0": 6, "y1": 16, @@ -19379,6 +21617,7 @@ Array [ "x": 0, "y": 1, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -19388,6 +21627,7 @@ Array [ "x": 1, "y": 2, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -19397,6 +21637,7 @@ Array [ "x": 2, "y": 10, }, + "mark": null, "x": 2, "y0": null, "y1": 10, @@ -19406,6 +21647,7 @@ Array [ "x": 3, "y": 6, }, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -19432,6 +21674,7 @@ Array [ "y1": 1, "y2": 3, }, + "mark": null, "x": 0, "y0": null, "y1": 1, @@ -19442,6 +21685,7 @@ Array [ "y1": 2, "y2": 7, }, + "mark": null, "x": 1, "y0": null, "y1": 2, @@ -19452,6 +21696,7 @@ Array [ "y1": 1, "y2": 2, }, + "mark": null, "x": 2, "y0": null, "y1": 1, @@ -19462,6 +21707,7 @@ Array [ "y1": 6, "y2": 10, }, + "mark": null, "x": 3, "y0": null, "y1": 6, @@ -19483,6 +21729,7 @@ Array [ "y1": 1, "y2": 3, }, + "mark": null, "x": 0, "y0": null, "y1": 3, @@ -19493,6 +21740,7 @@ Array [ "y1": 2, "y2": 7, }, + "mark": null, "x": 1, "y0": null, "y1": 7, @@ -19503,6 +21751,7 @@ Array [ "y1": 1, "y2": 2, }, + "mark": null, "x": 2, "y0": null, "y1": 2, @@ -19513,6 +21762,7 @@ Array [ "y1": 6, "y2": 10, }, + "mark": null, "x": 3, "y0": null, "y1": 10, diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 37ffe5aa39..cee1e43200 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -442,7 +442,7 @@ export function getAvailableTicks( const firstTick = { value: firstTickValue, label: axisSpec.tickFormat(firstTickValue, tickFormatOptions), - position: scale.scale(firstTickValue) + offset, + position: (scale.scale(firstTickValue) ?? 0) + offset, }; const lastTickValue = firstTickValue + scale.minInterval; @@ -469,7 +469,7 @@ export function enableDuplicatedTicks( return { value: tick, label: axisSpec.tickFormat(tick, tickFormatOptions), - position: scale.scale(tick) + offset, + position: (scale.scale(tick) ?? 0) + offset, }; }); diff --git a/src/chart_types/xy_chart/utils/indexed_geometry_linear_map.ts b/src/chart_types/xy_chart/utils/indexed_geometry_linear_map.ts new file mode 100644 index 0000000000..24580ac299 --- /dev/null +++ b/src/chart_types/xy_chart/utils/indexed_geometry_linear_map.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 { IndexedGeometry } from '../../../utils/geometry'; + +/** @internal */ +export class IndexedGeometryLinearMap { + private map = new Map(); + + get size() { + return this.map.size; + } + + set(geometry: IndexedGeometry) { + const { x } = geometry.value; + const existing = this.map.get(x); + if (existing === undefined) { + this.map.set(x, [geometry]); + } else { + this.map.set(x, [geometry, ...existing]); + } + } + + getMergeData() { + return [...this.map.values()]; + } + + keys(): Array { + return [...this.map.keys()]; + } + + find(x: number | string | null): IndexedGeometry[] { + if (x === null) { + return []; + } + + return this.map.get(x) ?? []; + } +} diff --git a/src/chart_types/xy_chart/utils/indexed_geometry_map.ts b/src/chart_types/xy_chart/utils/indexed_geometry_map.ts new file mode 100644 index 0000000000..31ad08c622 --- /dev/null +++ b/src/chart_types/xy_chart/utils/indexed_geometry_map.ts @@ -0,0 +1,100 @@ +/* + * 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 { $Values } from 'utility-types'; + +import { IndexedGeometry, isPointGeometry } from '../../../utils/geometry'; +import { Point } from '../../../utils/point'; +import { IndexedGeometryLinearMap } from './indexed_geometry_linear_map'; +import { IndexedGeometrySpatialMap } from './indexed_geometry_spatial_map'; +import { Bounds } from '../../../utils/d3-delaunay'; + +/** @internal */ +export const GeometryType = Object.freeze({ + linear: 'linear' as 'linear', + spatial: 'spatial' as 'spatial', +}); +/** @internal */ +export type GeometryType = $Values; + +/** @internal */ +export class IndexedGeometryMap { + private linearMap = new IndexedGeometryLinearMap(); + private spatialMap = new IndexedGeometrySpatialMap(); + + /** + * Returns triangulation instance to render spatial grid + * + * @param bounds + */ + triangulation(bounds?: Bounds) { + return this.spatialMap.triangulation(bounds); + } + + keys(): Array { + return [...this.linearMap.keys(), ...this.spatialMap.keys()]; + } + + get size(): number { + return this.linearMap.size + this.spatialMap.size; + } + + set(geometry: IndexedGeometry, type: GeometryType = GeometryType.linear) { + if (type === GeometryType.spatial && isPointGeometry(geometry)) { + // TODO: Add dev error here when attempting spatial upset with non-point + this.spatialMap.set([geometry]); + } else { + this.linearMap.set(geometry); + } + } + + find(x: number | string | null, point?: Point): IndexedGeometry[] { + if (x === null && !point) { + return []; + } + + const spatialValues = point === undefined ? [] : this.spatialMap.find(point); + + return [...this.linearMap.find(x), ...spatialValues]; + } + + getMergeData() { + return { + spatialGeometries: this.spatialMap.getMergeData(), + linearGeometries: this.linearMap.getMergeData(), + }; + } + + /** + * Merge multiple indexedMaps into base indexedMaps + * @param indexedMaps + */ + merge(...indexedMaps: IndexedGeometryMap[]) { + for (const indexedMap of indexedMaps) { + const { spatialGeometries, linearGeometries } = indexedMap.getMergeData(); + this.spatialMap.set(spatialGeometries); + linearGeometries.forEach((geometry) => { + if (Array.isArray(geometry)) { + geometry.forEach((geometry) => this.linearMap.set(geometry)); + } else { + this.linearMap.set(geometry); + } + }); + } + } +} diff --git a/src/chart_types/xy_chart/utils/indexed_geometry_spatial_map.ts b/src/chart_types/xy_chart/utils/indexed_geometry_spatial_map.ts new file mode 100644 index 0000000000..df2e30c53a --- /dev/null +++ b/src/chart_types/xy_chart/utils/indexed_geometry_spatial_map.ts @@ -0,0 +1,132 @@ +/* + * 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 { IndexedGeometry, PointGeometry } from '../../../utils/geometry'; +import { Point } from '../../../utils/point'; +import { getDistance } from '../state/utils'; +import { Delaunay, Bounds } from '../../../utils/d3-delaunay'; +import { DEFAULT_HIGHLIGHT_PADDING } from '../rendering/rendering'; + +/** @internal */ +export type IndexedGeometrySpatialMapPoint = [number, number]; + +/** @internal */ +export class IndexedGeometrySpatialMap { + private map: Delaunay | null = null; + private points: IndexedGeometrySpatialMapPoint[] = []; + private pointGeometries: PointGeometry[] = []; + private searchStartIndex: number = 0; + private maxRadius = -Infinity; + + constructor(points: PointGeometry[] = []) { + this.set(points); + } + + get size() { + return this.points.length; + } + + isSpatial() { + return this.pointGeometries.length > 0; + } + + set(points: PointGeometry[]) { + this.maxRadius = Math.max(this.maxRadius, ...points.map(({ radius }) => radius)); + this.pointGeometries.push(...points); + this.points.push( + ...points.map(({ x, y }) => { + // TODO: handle coincident points better + // This nonce is used to slightly offset every point such that each point + // has a unique poition in the index. This number is only used in the index. + // The other option would be to find the point(s) near a Point and add logic + // to account for multiple values in the pointGeometries array. This would be + // a very comutationally expensive approach having to repeat for every point. + const nonce = Math.random() * 0.000001; + return [x + nonce, y]; + }), + ); + + if (this.points.length > 0) { + // TODO: handle write/read init + this.map = Delaunay.from(this.points); + } + } + + triangulation = (bounds?: Bounds) => { + return this.map?.voronoi(bounds); + }; + + getMergeData() { + return [...this.pointGeometries]; + } + + keys(): Array { + return this.pointGeometries.map(({ value: { x } }) => x); + } + + find(point: Point): IndexedGeometry[] { + const elements = []; + if (this.map !== null) { + const index = this.map.find(point.x, point.y, this.searchStartIndex); + const geometry = this.pointGeometries[index]; + + if (geometry) { + // Set next starting search index for faster lookup + this.searchStartIndex = index; + elements.push(geometry); + elements.push(...this.getRadialNeighbors(index, point, new Set([index]))); + } + } + + return elements; + } + + /** + * Gets surrounding points whose radius could be within the active cursor position + * + * @param selectedIndex + * @param point + * @param visitedIndices + */ + private getRadialNeighbors(selectedIndex: number, point: Point, visitedIndices: Set): IndexedGeometry[] { + if (this.map === null) { + return []; + } + + const neighbors = [...this.map.neighbors(selectedIndex)]; + return neighbors.reduce((acc, i) => { + if (visitedIndices.has(i)) { + return acc; + } + + visitedIndices.add(i); + const geometry = this.pointGeometries[i]; + + if (geometry) { + acc.push(geometry); + + if (getDistance(geometry, point) < Math.min(this.maxRadius, DEFAULT_HIGHLIGHT_PADDING)) { + // Gets neighbors based on relation to maxRadius + acc.push(...this.getRadialNeighbors(i, point, visitedIndices)); + } + } + + return acc; + }, []); + } +} diff --git a/src/chart_types/xy_chart/utils/interactions.test.ts b/src/chart_types/xy_chart/utils/interactions.test.ts index aa89a1e56b..e6a01c7950 100644 --- a/src/chart_types/xy_chart/utils/interactions.test.ts +++ b/src/chart_types/xy_chart/utils/interactions.test.ts @@ -57,6 +57,7 @@ const ig1: IndexedGeometry = { accessor: 'y1', x: 0, y: 1, + mark: null, }, x: 0, y: 0, @@ -76,6 +77,7 @@ const ig2: IndexedGeometry = { accessor: 'y1', x: 0, y: 1, + mark: null, }, color: 'red', x: 0, @@ -96,6 +98,7 @@ const ig3: IndexedGeometry = { accessor: 'y1', x: 123, y: 123, + mark: null, }, color: 'red', @@ -117,6 +120,7 @@ const ig4: IndexedGeometry = { accessor: 'y1', x: 123, y: 123, + mark: null, }, color: 'blue', x: 0, @@ -137,6 +141,7 @@ const ig5: IndexedGeometry = { accessor: 'y1', x: 123, y: 123, + mark: null, }, color: 'red', x: 0, @@ -157,6 +162,7 @@ const ig6: PointGeometry = { accessor: 'y1', x: 123, y: 123, + mark: null, }, color: 'red', x: 0, 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 f70c2e0e6d..fc3b29b22a 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 @@ -41,6 +41,7 @@ const STANDARD_DATA_SET: RawDataSeries[] = [ { x: 0, y1: 10, + mark: null, }, ], seriesKeys: [], @@ -54,6 +55,7 @@ const STANDARD_DATA_SET: RawDataSeries[] = [ { x: 0, y1: 20, + mark: null, }, ], seriesKeys: [], @@ -67,6 +69,7 @@ const STANDARD_DATA_SET: RawDataSeries[] = [ { x: 0, y1: 30, + mark: null, }, ], seriesKeys: [], @@ -82,6 +85,7 @@ const WITH_NULL_DATASET: RawDataSeries[] = [ { x: 0, y1: 10, + mark: null, }, ], seriesKeys: [], @@ -95,6 +99,7 @@ const WITH_NULL_DATASET: RawDataSeries[] = [ { x: 0, y1: null, + mark: null, }, ], seriesKeys: [], @@ -108,6 +113,7 @@ const WITH_NULL_DATASET: RawDataSeries[] = [ { x: 0, y1: 30, + mark: null, }, ], seriesKeys: [], @@ -124,6 +130,7 @@ const STANDARD_DATA_SET_WY0: RawDataSeries[] = [ x: 0, y0: 2, y1: 10, + mark: null, }, ], seriesKeys: [], @@ -138,6 +145,7 @@ const STANDARD_DATA_SET_WY0: RawDataSeries[] = [ x: 0, y0: 4, y1: 20, + mark: null, }, ], seriesKeys: [], @@ -152,6 +160,7 @@ const STANDARD_DATA_SET_WY0: RawDataSeries[] = [ x: 0, y0: 6, y1: 30, + mark: null, }, ], seriesKeys: [], @@ -168,6 +177,7 @@ const WITH_NULL_DATASET_WY0: RawDataSeries[] = [ x: 0, y0: 2, y1: 10, + mark: null, }, ], seriesKeys: [], @@ -181,6 +191,7 @@ const WITH_NULL_DATASET_WY0: RawDataSeries[] = [ { x: 0, y1: null, + mark: null, }, ], seriesKeys: [], @@ -195,6 +206,7 @@ const WITH_NULL_DATASET_WY0: RawDataSeries[] = [ x: 0, y0: 6, y1: 30, + mark: null, }, ], seriesKeys: [], @@ -212,9 +224,9 @@ const DATA_SET_WITH_NULL_2: RawDataSeries[] = [ seriesKeys: ['a'], key: 'a', data: [ - { x: 1, y1: 1 }, - { x: 2, y1: 2 }, - { x: 4, y1: 4 }, + { x: 1, y1: 1, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 4, y1: 4, mark: null }, ], }, { @@ -224,8 +236,8 @@ const DATA_SET_WITH_NULL_2: RawDataSeries[] = [ seriesKeys: ['b'], key: 'b', data: [ - { x: 1, y1: 21 }, - { x: 3, y1: 23 }, + { x: 1, y1: 21, mark: null }, + { x: 3, y1: 23, mark: null }, ], }, ]; @@ -254,6 +266,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y0: 0, y1: 10, + mark: null, }); expect(formattedData[1].data[0]).toEqual({ datum: undefined, @@ -262,6 +275,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y0: 0, y1: 20, + mark: null, }); expect(formattedData[2].data[0]).toEqual({ datum: undefined, @@ -270,6 +284,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y0: 0, y1: 30, + mark: null, }); formattedData = testModule.formatNonStackedDataSeriesValues( STANDARD_DATA_SET, @@ -284,6 +299,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y0: 10, y1: 10, + mark: null, }); expect(formattedData[1].data[0]).toEqual({ datum: undefined, @@ -292,6 +308,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y0: 20, y1: 20, + mark: null, }); expect(formattedData[2].data[0]).toEqual({ datum: undefined, @@ -300,6 +317,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y0: 30, y1: 30, + mark: null, }); }); test('format data with nulls', () => { @@ -316,6 +334,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y1: null, y0: null, + mark: null, }); }); test('format data without nulls with y0 values', () => { @@ -332,6 +351,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y0: 2, y1: 10, + mark: null, }); expect(formattedData[1].data[0]).toEqual({ datum: undefined, @@ -340,6 +360,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y0: 4, y1: 20, + mark: null, }); expect(formattedData[2].data[0]).toEqual({ datum: undefined, @@ -348,6 +369,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y0: 6, y1: 30, + mark: null, }); }); test('format data with nulls', () => { @@ -364,6 +386,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y0: 2, y1: 10, + mark: null, }); expect(formattedData[1].data[0]).toEqual({ datum: undefined, @@ -372,6 +395,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y1: null, y0: null, + mark: null, }); expect(formattedData[2].data[0]).toEqual({ datum: undefined, @@ -380,6 +404,7 @@ describe('Non-Stacked Series Utils', () => { x: 0, y0: 6, y1: 30, + mark: null, }); }); test('format data without nulls on second series', () => { @@ -400,6 +425,7 @@ describe('Non-Stacked Series Utils', () => { x: 1, y0: 0, y1: 1, + mark: null, }); expect(formattedData[0].data[1]).toEqual({ datum: undefined, @@ -408,6 +434,7 @@ describe('Non-Stacked Series Utils', () => { x: 2, y0: 0, y1: 2, + mark: null, }); expect(formattedData[0].data[2]).toEqual({ datum: undefined, @@ -416,6 +443,7 @@ describe('Non-Stacked Series Utils', () => { x: 4, y0: 0, y1: 4, + mark: null, }); expect(formattedData[1].data[0]).toEqual({ datum: undefined, @@ -424,6 +452,7 @@ describe('Non-Stacked Series Utils', () => { x: 1, y0: 0, y1: 21, + mark: null, }); expect(formattedData[1].data[1]).toEqual({ datum: undefined, @@ -432,6 +461,7 @@ describe('Non-Stacked Series Utils', () => { x: 3, y0: 0, y1: 23, + mark: null, }); }); }); diff --git a/src/chart_types/xy_chart/utils/nonstacked_series_utils.ts b/src/chart_types/xy_chart/utils/nonstacked_series_utils.ts index bc39e33339..7cdfdd959c 100644 --- a/src/chart_types/xy_chart/utils/nonstacked_series_utils.ts +++ b/src/chart_types/xy_chart/utils/nonstacked_series_utils.ts @@ -20,7 +20,7 @@ import { DataSeries, DataSeriesDatum, RawDataSeries } from './series'; import { fitFunction } from './fit_function'; import { isAreaSeriesSpec, isLineSeriesSpec, SeriesSpecs, BasicSeriesSpec } from './specs'; import { ScaleType } from '../../../scales'; -import { getSpecsById } from '../state/utils'; +import { getSpecsById, isDefined } from '../state/utils'; /** @internal */ export const formatNonStackedDataSeriesValues = ( @@ -59,7 +59,7 @@ export const formatNonStackedDataValues = (dataSeries: RawDataSeries, scaleToExt }; for (let i = 0; i < len; i++) { const data = dataSeries.data[i]; - const { x, y1, datum } = data; + const { x, y1, mark, datum } = data; let y0: number | null; if (y1 === null) { y0 = null; @@ -77,6 +77,7 @@ export const formatNonStackedDataValues = (dataSeries: RawDataSeries, scaleToExt y0, initialY1: y1, initialY0: data.y0 == null || y1 === null ? null : data.y0, + mark: isDefined(mark) ? mark : null, datum, }; formattedValues.data.push(formattedValue); diff --git a/src/chart_types/xy_chart/utils/series.test.ts b/src/chart_types/xy_chart/utils/series.test.ts index 858b4442ba..ea760d1fb8 100644 --- a/src/chart_types/xy_chart/utils/series.test.ts +++ b/src/chart_types/xy_chart/utils/series.test.ts @@ -111,9 +111,9 @@ describe('Series', () => { seriesKeys: ['a'], key: 'a', data: [ - { x: 1, y1: 1 }, - { x: 2, y1: 2 }, - { x: 4, y1: 4 }, + { x: 1, y1: 1, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 4, y1: 4, mark: null }, ], }, { @@ -123,8 +123,8 @@ describe('Series', () => { seriesKeys: ['b'], key: 'b', data: [ - { x: 1, y1: 21 }, - { x: 3, y1: 23 }, + { x: 1, y1: 21, mark: null }, + { x: 3, y1: 23, mark: null }, ], }, ]; @@ -141,10 +141,10 @@ describe('Series', () => { seriesKeys: ['a'], key: 'a', data: [ - { x: 1, y1: 1 }, - { x: 2, y1: 2 }, - { x: 3, y1: 3 }, - { x: 4, y1: 4 }, + { x: 1, y1: 1, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 3, y1: 3, mark: null }, + { x: 4, y1: 4, mark: null }, ], }, { @@ -154,10 +154,10 @@ describe('Series', () => { seriesKeys: ['b'], key: 'b', data: [ - { x: 1, y1: 1 }, - { x: 2, y1: 2 }, - { x: 3, y1: 3 }, - { x: 4, y1: 4 }, + { x: 1, y1: 1, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 3, y1: 3, mark: null }, + { x: 4, y1: 4, mark: null }, ], }, { @@ -167,10 +167,10 @@ describe('Series', () => { seriesKeys: ['b'], key: 'b', data: [ - { x: 1, y1: 1 }, - { x: 2, y1: 2 }, - { x: 3, y1: 3 }, - { x: 4, y1: 4 }, + { x: 1, y1: 1, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 3, y1: 3, mark: null }, + { x: 4, y1: 4, mark: null }, ], }, { @@ -180,10 +180,10 @@ describe('Series', () => { seriesKeys: ['b'], key: 'b', data: [ - { x: 1, y1: 1 }, - { x: 2, y1: 2 }, - { x: 3, y1: 3 }, - { x: 4, y1: 4 }, + { x: 1, y1: 1, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 3, y1: 3, mark: null }, + { x: 4, y1: 4, mark: null }, ], }, ]; @@ -200,9 +200,9 @@ describe('Series', () => { seriesKeys: ['a'], key: 'a', data: [ - { x: 1, y1: 1 }, - { x: 4, y1: 4 }, - { x: 2, y1: 2 }, + { x: 1, y1: 1, mark: null }, + { x: 4, y1: 4, mark: null }, + { x: 2, y1: 2, mark: null }, ], }, { @@ -212,8 +212,8 @@ describe('Series', () => { seriesKeys: ['b'], key: 'b', data: [ - { x: 3, y1: 23 }, - { x: 1, y1: 21 }, + { x: 3, y1: 23, mark: null }, + { x: 1, y1: 21, mark: null }, ], }, ]; @@ -230,7 +230,7 @@ describe('Series', () => { splitAccessors: new Map(), seriesKeys: ['a'], key: 'a', - data: new Array(maxArrayItems).fill(0).map((d, i) => ({ x: i, y1: i })), + data: new Array(maxArrayItems).fill(0).map((d, i) => ({ x: i, y1: i, mark: null })), }, { specId: 'spec1', @@ -238,7 +238,7 @@ describe('Series', () => { splitAccessors: new Map(), seriesKeys: ['b'], key: 'b', - data: new Array(maxArrayItems).fill(0).map((d, i) => ({ x: i, y1: i })), + data: new Array(maxArrayItems).fill(0).map((d, i) => ({ x: i, y1: i, mark: null })), }, ]; const xValues = new Set(new Array(maxArrayItems).fill(0).map((d, i) => i)); @@ -254,9 +254,9 @@ describe('Series', () => { seriesKeys: ['a'], key: 'a', data: [ - { x: 1, y1: 1 }, - { x: 2, y1: 2 }, - { x: 4, y1: 4 }, + { x: 1, y1: 1, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 4, y1: 4, mark: null }, ], }, { @@ -266,8 +266,8 @@ describe('Series', () => { seriesKeys: ['b'], key: 'b', data: [ - { x: 1, y1: 21 }, - { x: 3, y1: 23 }, + { x: 1, y1: 21, mark: null }, + { x: 3, y1: 23, mark: null }, ], }, ]; @@ -286,10 +286,10 @@ describe('Series', () => { seriesKeys: ['a'], key: 'a', data: [ - { x: 1, y1: 1 }, - { x: 2, y1: 2 }, - { x: 3, y1: 3 }, - { x: 4, y1: 4 }, + { x: 1, y1: 1, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 3, y1: 3, mark: null }, + { x: 4, y1: 4, mark: null }, ], }, { @@ -299,10 +299,10 @@ describe('Series', () => { seriesKeys: ['b'], key: 'b', data: [ - { x: 1, y1: 1 }, - { x: 2, y1: 2 }, - { x: 3, y1: 3 }, - { x: 4, y1: 4 }, + { x: 1, y1: 1, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 3, y1: 3, mark: null }, + { x: 4, y1: 4, mark: null }, ], }, { @@ -312,10 +312,10 @@ describe('Series', () => { seriesKeys: ['b'], key: 'b', data: [ - { x: 1, y1: 1 }, - { x: 2, y1: 2 }, - { x: 3, y1: 3 }, - { x: 4, y1: 4 }, + { x: 1, y1: 1, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 3, y1: 3, mark: null }, + { x: 4, y1: 4, mark: null }, ], }, { @@ -325,10 +325,10 @@ describe('Series', () => { seriesKeys: ['b'], key: 'b', data: [ - { x: 1, y1: 1 }, - { x: 2, y1: 2 }, - { x: 3, y1: 3 }, - { x: 4, y1: 4 }, + { x: 1, y1: 1, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 3, y1: 3, mark: null }, + { x: 4, y1: 4, mark: null }, ], }, ]; @@ -347,9 +347,9 @@ describe('Series', () => { seriesKeys: ['a'], key: 'a', data: [ - { x: 1, y1: 3, y0: 1 }, - { x: 2, y1: 3, y0: 2 }, - { x: 4, y1: 4, y0: 3 }, + { x: 1, y1: 3, y0: 1, mark: null }, + { x: 2, y1: 3, y0: 2, mark: null }, + { x: 4, y1: 4, y0: 3, mark: null }, ], }, { @@ -359,10 +359,10 @@ describe('Series', () => { seriesKeys: ['b'], key: 'b', data: [ - { x: 1, y1: 2, y0: 1 }, - { x: 2, y1: 3, y0: 1 }, - { x: 3, y1: 23, y0: 4 }, - { x: 4, y1: 4, y0: 1 }, + { x: 1, y1: 2, y0: 1, mark: null }, + { x: 2, y1: 3, y0: 1, mark: null }, + { x: 3, y1: 23, y0: 4, mark: null }, + { x: 4, y1: 4, y0: 1, mark: null }, ], }, ]; @@ -392,9 +392,9 @@ describe('Series', () => { seriesKeys: ['a'], key: 'a', data: [ - { x: 1, y1: 3, y0: 1 }, - { x: 2, y1: 3, y0: 2 }, - { x: 4, y1: 4, y0: 3 }, + { x: 1, y1: 3, y0: 1, mark: null }, + { x: 2, y1: 3, y0: 2, mark: null }, + { x: 4, y1: 4, y0: 3, mark: null }, ], }, { @@ -404,10 +404,10 @@ describe('Series', () => { seriesKeys: ['b'], key: 'b', data: [ - { x: 1, y1: 2, y0: 1 }, - { x: 2, y1: 3, y0: 1 }, - { x: 3, y1: 23, y0: 4 }, - { x: 4, y1: 4, y0: 1 }, + { x: 1, y1: 2, y0: 1, mark: null }, + { x: 2, y1: 3, y0: 1, mark: null }, + { x: 3, y1: 23, y0: 4, mark: null }, + { x: 4, y1: 4, y0: 1, mark: null }, ], }, ]; diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index c21fdd470a..f6863e2e87 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -47,10 +47,12 @@ export interface RawDataSeriesDatum { x: number | string; /** the main y metric */ y1: number | null; - /** the optional y0 metric, used for bars or area with a lower bound */ + /** the optional y0 metric, used for bars and area with a lower bound */ y0?: number | null; + /** the optional mark metric, used for lines and area series */ + mark?: number | null; /** the datum */ - datum?: T; + datum?: T | null; } /** @internal */ @@ -65,8 +67,10 @@ export interface DataSeriesDatum { initialY1: number | null; /** initial y0 value, non stacked */ initialY0: number | null; + /** the optional mark metric, used for lines and area series */ + mark: number | null; /** initial datum */ - datum?: T; + datum: T; /** the list of filled values because missing or nulls */ filled?: FilledValues; } @@ -131,8 +135,12 @@ export function splitSeries({ xAccessor, yAccessors, y0Accessors, + markSizeAccessor, splitSeriesAccessors = [], -}: Pick): { +}: Pick< + BasicSeriesSpec, + 'id' | 'data' | 'xAccessor' | 'yAccessors' | 'y0Accessors' | 'splitSeriesAccessors' | 'markSizeAccessor' +>): { rawDataSeries: RawDataSeries[]; colorsValues: Set; xValues: Set; @@ -151,7 +159,13 @@ export function splitSeries({ if (isMultipleY) { yAccessors.forEach((accessor, index) => { - const cleanedDatum = cleanDatum(datum, xAccessor, accessor, y0Accessors && y0Accessors[index]); + const cleanedDatum = cleanDatum( + datum, + xAccessor, + accessor, + y0Accessors && y0Accessors[index], + markSizeAccessor, + ); if (cleanedDatum !== null && cleanedDatum.x !== null && cleanedDatum.x !== undefined) { xValues.add(cleanedDatum.x); @@ -160,7 +174,7 @@ export function splitSeries({ } }); } else { - const cleanedDatum = cleanDatum(datum, xAccessor, yAccessors[0], y0Accessors && y0Accessors[0]); + const cleanedDatum = cleanDatum(datum, xAccessor, yAccessors[0], y0Accessors && y0Accessors[0], markSizeAccessor); if (cleanedDatum !== null && cleanedDatum.x !== null && cleanedDatum.x !== undefined) { xValues.add(cleanedDatum.x); const seriesKey = updateSeriesMap(series, splitAccessors, yAccessors[0], cleanedDatum, specId); @@ -252,17 +266,21 @@ export function cleanDatum( xAccessor: Accessor | AccessorFn, yAccessor: Accessor, y0Accessor?: Accessor, + markSizeAccessor?: Accessor | AccessorFn, ): RawDataSeriesDatum | null { if (typeof datum !== 'object' || datum === null) { return null; } + const x = getAccessorValue(datum, xAccessor); + if (typeof x !== 'string' && typeof x !== 'number') { return null; } - const y1 = castToNumber(datum[yAccessor as keyof typeof datum]); - const cleanedDatum: RawDataSeriesDatum = { x, y1, datum, y0: null }; + const mark = markSizeAccessor === undefined ? null : getAccessorValue(datum, markSizeAccessor); + const y1 = castToNumber(datum[yAccessor]); + const cleanedDatum: RawDataSeriesDatum = { x, y1, datum, y0: null, mark }; if (y0Accessor) { cleanedDatum.y0 = castToNumber(datum[y0Accessor as keyof typeof datum]); } diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index 81138d6500..6af319aa6f 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -25,9 +25,10 @@ import { LineSeriesStyle, PointStyle, RectAnnotationStyle, + BubbleSeriesStyle, } from '../../../utils/themes/theme'; -import { Accessor, AccessorFormat, AccessorFn } from '../../../utils/accessor'; import { RecursivePartial, Color, Position, Datum } from '../../../utils/commons'; +import { Accessor, AccessorFormat, AccessorFn } from '../../../utils/accessor'; import { AxisId, GroupId } from '../../../utils/ids'; import { ScaleContinuousType, ScaleType } from '../../../scales'; import { CurveType } from '../../../utils/curves'; @@ -43,8 +44,8 @@ export const SeriesTypes = Object.freeze({ Area: 'area' as 'area', Bar: 'bar' as 'bar', Line: 'line' as 'line', + Bubble: 'bubble' as 'bubble', }); - export type SeriesTypes = $Values; /** @@ -338,6 +339,8 @@ export interface SeriesAccessors { splitSeriesAccessors?: Accessor[]; /** An array of fields thats indicates the stack membership */ stackAccessors?: Accessor[]; + /** Field name of mark size metric on `Datum` */ + markSizeAccessor?: Accessor | AccessorFn; } export interface SeriesScales { @@ -443,6 +446,21 @@ export type LineSeriesSpec = BasicSeriesSpec & fit?: Exclude | FitConfig; }; +/** + * This spec describe the dataset configuration used to display a line series. + * + * @alpha + */ +export type BubbleSeriesSpec = BasicSeriesSpec & { + /** @default bubble */ + seriesType: typeof SeriesTypes.Bubble; + bubbleSeriesStyle?: RecursivePartial; + /** + * An optional functional accessor to return custom color or style for point datum + */ + pointStyleAccessor?: PointStyleAccessor; +}; + /** * This spec describe the dataset configuration used to display an area series. */ @@ -682,26 +700,37 @@ export interface BaseAnnotationSpec< export type AnnotationSpec = LineAnnotationSpec | RectAnnotationSpec; +/** @internal */ export function isLineAnnotation(spec: AnnotationSpec): spec is LineAnnotationSpec { return spec.annotationType === AnnotationTypes.Line; } +/** @internal */ export function isRectAnnotation(spec: AnnotationSpec): spec is RectAnnotationSpec { return spec.annotationType === AnnotationTypes.Rectangle; } +/** @internal */ export function isBarSeriesSpec(spec: BasicSeriesSpec): spec is BarSeriesSpec { return spec.seriesType === SeriesTypes.Bar; } +/** @internal */ +export function isBubbleSeriesSpec(spec: BasicSeriesSpec): spec is BubbleSeriesSpec { + return spec.seriesType === SeriesTypes.Bubble; +} + +/** @internal */ export function isLineSeriesSpec(spec: BasicSeriesSpec): spec is LineSeriesSpec { return spec.seriesType === SeriesTypes.Line; } +/** @internal */ export function isAreaSeriesSpec(spec: BasicSeriesSpec): spec is AreaSeriesSpec { return spec.seriesType === SeriesTypes.Area; } +/** @internal */ export function isBandedSpec(y0Accessors: SeriesAccessors['y0Accessors']): boolean { return Boolean(y0Accessors && y0Accessors.length > 0); } 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 501ecba8eb..256aea4861 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 @@ -16,205 +16,59 @@ * specific language governing permissions and limitations * under the License. */ -import { RawDataSeries } from './series'; import { formatStackedDataSeriesValues } from './stacked_series_utils'; import { ScaleType } from '../../../scales'; +import { MockRawDataSeries } from '../../../mocks'; describe('Stacked Series Utils', () => { - const STANDARD_DATA_SET: RawDataSeries[] = [ - { - data: [ - { - x: 0, - y1: 10, - }, - ], - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [], - key: 'color-key', - specId: 'spec1', - }, - { - data: [ - { - x: 0, - y1: 20, - }, - ], - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [], - key: 'color-key', - specId: 'spec2', - }, - { - data: [ - { - x: 0, - y1: 70, - }, - ], - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [], - key: 'color-key', - specId: 'spec3', - }, - ]; - const WITH_NULL_DATASET: RawDataSeries[] = [ - { - data: [ - { - x: 0, - y1: 10, - }, - ], - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [], - key: 'color-key', - specId: 'spec1', - }, - { - data: [ - { - x: 0, - y1: null, - }, - ], - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [], - key: 'color-key', - specId: 'spec2', - }, - { - data: [ - { - x: 0, - y1: 30, - }, - ], - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [], - key: 'color-key', - specId: 'spec3', - }, - ]; - const STANDARD_DATA_SET_WY0: RawDataSeries[] = [ - { - data: [ - { - x: 0, - y0: 2, - y1: 10, - }, - ], - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [], - key: 'color-key', - specId: 'spec1', - }, - { - data: [ - { - x: 0, - y0: 4, - y1: 20, - }, - ], - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [], - key: 'color-key', - specId: 'spec2', - }, - { - data: [ - { - x: 0, - y0: 6, - y1: 70, - }, - ], - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [], - key: 'color-key', - specId: 'spec3', - }, - ]; - const WITH_NULL_DATASET_WY0: RawDataSeries[] = [ - { - data: [ - { - x: 0, - y0: 2, - y1: 10, - }, - ], - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: [], - key: 'color-key', - specId: 'spec1', - }, + const STANDARD_DATA_SET = MockRawDataSeries.fromData([[{ x: 0, y1: 10 }], [{ x: 0, y1: 20 }], [{ x: 0, y1: 70 }]], { + yAccessor: 'y1', + splitAccessors: new Map(), + seriesKeys: [], + }); + const WITH_NULL_DATASET = MockRawDataSeries.fromData([[{ x: 0, y1: 10 }], [{ x: 0, y1: null }], [{ x: 0, y1: 30 }]], { + yAccessor: 'y1', + seriesKeys: [], + }); + const STANDARD_DATA_SET_WY0 = MockRawDataSeries.fromData( + [[{ x: 0, y0: 2, y1: 10 }], [{ x: 0, y0: 4, y1: 20 }], [{ x: 0, y0: 6, y1: 70 }]], { - data: [ - { - x: 0, - y1: null, - }, - ], yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [], - key: 'color-key', - specId: 'spec2', }, + ); + const WITH_NULL_DATASET_WY0 = MockRawDataSeries.fromData( + [[{ x: 0, y0: 2, y1: 10 }], [{ x: 0, y1: null }], [{ x: 0, y0: 6, y1: 90, mark: null }]], { - data: [ - { - x: 0, - y0: 6, - y1: 90, - }, - ], yAccessor: 'y1', - splitAccessors: new Map(), seriesKeys: [], - key: 'color-key', - specId: 'spec3', - }, - ]; - const DATA_SET_WITH_NULL_2: RawDataSeries[] = [ - { - specId: 'spec1', - yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: ['a'], - key: 'a', - data: [ - { x: 1, y1: 10 }, - { x: 2, y1: 20 }, - { x: 4, y1: 40 }, - ], }, + ); + const DATA_SET_WITH_NULL_2 = MockRawDataSeries.defaults( + [ + { + seriesKeys: ['a'], + key: 'a', + data: [ + { x: 1, y1: 10 }, + { x: 2, y1: 20 }, + { x: 4, y1: 40 }, + ], + }, + { + seriesKeys: ['b'], + key: 'b', + data: [ + { x: 1, y1: 90 }, + { x: 3, y1: 30 }, + ], + }, + ], { specId: 'spec1', yAccessor: 'y1', - splitAccessors: new Map(), - seriesKeys: ['b'], - key: 'b', - data: [ - { x: 1, y1: 90 }, - { x: 3, y1: 30 }, - ], }, - ]; + ); const xValues = new Set([0]); const with2NullsXValues = new Set([1, 2, 3, 4]); @@ -243,13 +97,13 @@ describe('Stacked Series Utils', () => { expect(data0.y0).toBeNull(); expect(data0.y1).toBe(0.25); - expect(formattedData[1].data[0]).toEqual({ - datum: undefined, + expect(formattedData[1].data[0]).toMatchObject({ initialY0: null, initialY1: null, x: 0, y1: null, y0: 0.25, + mark: null, }); const data2 = formattedData[2].data[0]; @@ -320,57 +174,57 @@ describe('Stacked Series Utils', () => { expect(formattedData.length).toBe(2); expect(formattedData[0].data.length).toBe(4); expect(formattedData[1].data.length).toBe(4); - expect(formattedData[0].data[0]).toEqual({ - datum: undefined, + expect(formattedData[0].data[0]).toMatchObject({ initialY0: null, initialY1: 0.1, x: 1, y0: null, y1: 0.1, + mark: null, }); - expect(formattedData[0].data[1]).toEqual({ - datum: undefined, + expect(formattedData[0].data[1]).toMatchObject({ initialY0: null, initialY1: 1, x: 2, y0: null, y1: 1, + mark: null, }); - expect(formattedData[0].data[3]).toEqual({ - datum: undefined, + expect(formattedData[0].data[3]).toMatchObject({ initialY0: null, initialY1: 1, x: 4, y0: null, y1: 1, + mark: null, }); - expect(formattedData[1].data[0]).toEqual({ - datum: undefined, + expect(formattedData[1].data[0]).toMatchObject({ initialY0: null, initialY1: 0.9, x: 1, y0: 0.1, y1: 1, + mark: null, }); - expect(formattedData[1].data[1]).toEqual({ - datum: undefined, + expect(formattedData[1].data[1]).toMatchObject({ initialY0: null, initialY1: 0, x: 2, y0: 1, y1: 1, + mark: null, filled: { x: 2, y1: 0, }, }); - expect(formattedData[1].data[2]).toEqual({ - datum: undefined, + expect(formattedData[1].data[2]).toMatchObject({ initialY0: null, initialY1: 1, x: 3, y0: 0, y1: 1, + mark: null, }); }); }); 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 86ec0059eb..4a1398bc90 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 @@ -43,6 +43,7 @@ describe('Stacked Series Utils', () => { { x: 0, y1: 10, + mark: null, }, ], yAccessor: 'y1', @@ -56,6 +57,7 @@ describe('Stacked Series Utils', () => { { x: 0, y1: 20, + mark: null, }, ], yAccessor: 'y1', @@ -69,6 +71,7 @@ describe('Stacked Series Utils', () => { { x: 0, y1: 30, + mark: null, }, ], yAccessor: 'y1', @@ -84,6 +87,7 @@ describe('Stacked Series Utils', () => { { x: 0, y1: 10, + mark: null, }, ], yAccessor: 'y1', @@ -97,6 +101,7 @@ describe('Stacked Series Utils', () => { { x: 0, y1: null, + mark: null, }, ], yAccessor: 'y1', @@ -110,6 +115,7 @@ describe('Stacked Series Utils', () => { { x: 0, y1: 30, + mark: null, }, ], yAccessor: 'y1', @@ -126,6 +132,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: 2, y1: 10, + mark: null, }, ], yAccessor: 'y1', @@ -140,6 +147,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: 4, y1: 20, + mark: null, }, ], yAccessor: 'y1', @@ -154,6 +162,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: 6, y1: 30, + mark: null, }, ], yAccessor: 'y1', @@ -170,6 +179,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: 2, y1: 10, + mark: null, }, ], yAccessor: 'y1', @@ -183,6 +193,7 @@ describe('Stacked Series Utils', () => { { x: 0, y1: null, + mark: null, }, ], yAccessor: 'y1', @@ -197,6 +208,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: 6, y1: 30, + mark: null, }, ], yAccessor: 'y1', @@ -214,9 +226,9 @@ describe('Stacked Series Utils', () => { seriesKeys: ['a'], key: 'a', data: [ - { x: 1, y1: 1 }, - { x: 2, y1: 2 }, - { x: 4, y1: 4 }, + { x: 1, y1: 1, mark: null }, + { x: 2, y1: 2, mark: null }, + { x: 4, y1: 4, mark: null }, ], }, { @@ -226,8 +238,8 @@ describe('Stacked Series Utils', () => { seriesKeys: ['b'], key: 'b', data: [ - { x: 1, y1: 21 }, - { x: 3, y1: 23 }, + { x: 1, y1: 21, mark: null }, + { x: 3, y1: 23, mark: null }, ], }, ]; @@ -296,6 +308,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: null, y1: 10, + mark: null, }); expect(formattedData[1].data[0]).toEqual({ datum: undefined, @@ -304,6 +317,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: 10, y1: 30, + mark: null, }); expect(formattedData[2].data[0]).toEqual({ datum: undefined, @@ -312,6 +326,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: 30, y1: 60, + mark: null, }); }); test('format data with nulls', () => { @@ -323,6 +338,7 @@ describe('Stacked Series Utils', () => { x: 0, y1: null, y0: null, + mark: null, }); }); test('format data without nulls with y0 values', () => { @@ -340,6 +356,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: 2, y1: 10, + mark: null, }); expect(formattedData[1].data[0]).toEqual({ datum: undefined, @@ -348,6 +365,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: 14, y1: 30, + mark: null, }); expect(formattedData[2].data[0]).toEqual({ datum: undefined, @@ -356,6 +374,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: 36, y1: 60, + mark: null, }); }); test('format data with nulls', () => { @@ -373,6 +392,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: 2, y1: 10, + mark: null, }); expect(formattedData[1].data[0]).toEqual({ datum: undefined, @@ -381,6 +401,7 @@ describe('Stacked Series Utils', () => { x: 0, y1: null, y0: null, + mark: null, }); expect(formattedData[2].data[0]).toEqual({ datum: undefined, @@ -389,6 +410,7 @@ describe('Stacked Series Utils', () => { x: 0, y0: 16, y1: 40, + mark: null, }); }); test('format data without nulls on second series', () => { @@ -410,6 +432,7 @@ describe('Stacked Series Utils', () => { x: 1, y0: null, y1: 1, + mark: null, }); expect(formattedData[0].data[1]).toEqual({ datum: undefined, @@ -418,6 +441,7 @@ describe('Stacked Series Utils', () => { x: 2, y0: null, y1: 2, + mark: null, }); expect(formattedData[0].data[3]).toEqual({ datum: undefined, @@ -426,6 +450,7 @@ describe('Stacked Series Utils', () => { x: 4, y0: null, y1: 4, + mark: null, }); expect(formattedData[1].data[0]).toEqual({ datum: undefined, @@ -434,6 +459,7 @@ describe('Stacked Series Utils', () => { x: 1, y0: 1, y1: 22, + mark: null, }); expect(formattedData[1].data[2]).toEqual({ datum: undefined, @@ -442,6 +468,7 @@ describe('Stacked Series Utils', () => { x: 3, y0: 0, y1: 23, + mark: null, }); }); }); @@ -452,7 +479,7 @@ describe('Stacked Series Utils', () => { percent: [0, 0, 0], total: 0, }); - const formattedDatum = getStackedFormattedSeriesDatum({ x: 1, y1: 0 }, stackedValues, 0, false, true); + const formattedDatum = getStackedFormattedSeriesDatum({ x: 1, y1: 0, mark: null }, stackedValues, 0, false, true); expect(formattedDatum).toEqual({ datum: undefined, initialY0: null, @@ -460,6 +487,7 @@ describe('Stacked Series Utils', () => { x: 1, y0: null, y1: 0, + mark: null, }); }); }); diff --git a/src/chart_types/xy_chart/utils/stacked_series_utils.ts b/src/chart_types/xy_chart/utils/stacked_series_utils.ts index 779c4496e3..dc67d79e4b 100644 --- a/src/chart_types/xy_chart/utils/stacked_series_utils.ts +++ b/src/chart_types/xy_chart/utils/stacked_series_utils.ts @@ -18,6 +18,7 @@ import { DataSeries, DataSeriesDatum, RawDataSeries, RawDataSeriesDatum, FilledValues } from './series'; import { ScaleType } from '../../../scales'; +import { isDefined } from '../state/utils'; /** @internal */ export interface StackedValues { @@ -151,6 +152,8 @@ export function formatStackedDataSeriesValues( x, // filling as 0 value y1: 0, + mark: null, + datum: null, }, stackedValues, seriesIndex, @@ -184,7 +187,7 @@ export function getStackedFormattedSeriesDatum( isPercentageMode = false, filled?: FilledValues, ): DataSeriesDatum | undefined { - const { x, datum } = data; + const { x, mark: markValue, datum } = data; const stack = stackedValues.get(x); if (!stack) { return; @@ -210,6 +213,7 @@ export function getStackedFormattedSeriesDatum( computedY0 = y0 ? y0 : null; } const initialY0 = y0 == null ? null : y0; + const mark = isDefined(markValue) ? markValue : null; if (seriesIndex === 0) { return { @@ -218,6 +222,7 @@ export function getStackedFormattedSeriesDatum( y0: computedY0, initialY1: y1, initialY0, + mark, datum, ...(filled && { filled }), }; @@ -249,6 +254,7 @@ export function getStackedFormattedSeriesDatum( y0: stackedY0, initialY1: y1, initialY0, + mark, datum, ...(filled && { filled }), }; diff --git a/src/components/tooltip/index.tsx b/src/components/tooltip/index.tsx index aaf9cf8bf8..dcf37459f0 100644 --- a/src/components/tooltip/index.tsx +++ b/src/components/tooltip/index.tsx @@ -99,41 +99,52 @@ class TooltipComponent extends React.Component { return
{formatter ? formatter(headerData) : headerData.value}
; } - render() { - const { isVisible, info, headerFormatter, getChartContainerRef } = this.props; - const chartContainerRef = getChartContainerRef(); - if (!this.portalNode || chartContainerRef.current === null || !isVisible || !info) { - return null; - } - const tooltipComponent = ( + renderTooltip = (info: TooltipInfo) => { + const { headerFormatter } = this.props; + + return (
{this.renderHeader(info.header, headerFormatter)}
- {info.values.map(({ seriesIdentifier, valueAccessor, label, value, color, isHighlighted, isVisible }) => { - if (!isVisible) { - return null; - } - const classes = classNames('echTooltip__item', { - /* eslint @typescript-eslint/camelcase:0 */ - echTooltip__rowHighlighted: isHighlighted, - }); - return ( -
- {label} - {value} -
- ); - })} + {info.values.map( + ({ seriesIdentifier, valueAccessor, label, value, markValue, color, isHighlighted, isVisible }, index) => { + if (!isVisible) { + return null; + } + const classes = classNames('echTooltip__item', { + /* eslint @typescript-eslint/camelcase:0 */ + echTooltip__rowHighlighted: isHighlighted, + }); + return ( +
+ {label} + {value} + {markValue &&  ({markValue})} +
+ ); + }, + )}
); - return createPortal(tooltipComponent, this.portalNode); + }; + + render() { + const { isVisible, info, getChartContainerRef } = this.props; + const chartContainerRef = getChartContainerRef(); + + if (!this.portalNode || chartContainerRef.current === null || !isVisible || !info) { + return null; + } + + return createPortal(this.renderTooltip(info), this.portalNode); } } diff --git a/src/mocks/geometries.ts b/src/mocks/geometries.ts new file mode 100644 index 0000000000..7b9c686710 --- /dev/null +++ b/src/mocks/geometries.ts @@ -0,0 +1,156 @@ +/* + * 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 { AreaGeometry, PointGeometry, BarGeometry, LineGeometry, BubbleGeometry } from '../utils/geometry'; +import { MockSeriesIdentifier } from './series/series_identifiers'; +import { mergePartial, RecursivePartial } from '../utils/commons'; +import { LIGHT_THEME } from '../utils/themes/light_theme'; +import { omit } from 'lodash'; + +const color = 'red'; +const { barSeriesStyle, lineSeriesStyle, areaSeriesStyle, bubbleSeriesStyle } = LIGHT_THEME; + +export class MockPointGeometry { + private static readonly base: PointGeometry = { + x: 0, + y: 0, + radius: 0, + color, + seriesIdentifier: MockSeriesIdentifier.default(), + styleOverrides: undefined, + value: { + accessor: 'y0', + x: 0, + y: 0, + mark: null, + }, + transform: { + x: 25, + y: 0, + }, + }; + + static default(partial?: RecursivePartial) { + return mergePartial(MockPointGeometry.base, partial, { mergeOptionalPartialValues: true }); + } + + static fromBaseline(baseline: RecursivePartial, omitKeys: string[] | string = []) { + return function(partial?: RecursivePartial) { + return omit( + mergePartial(MockPointGeometry.base, partial, { mergeOptionalPartialValues: true }, [baseline]), + omitKeys, + ); + }; + } +} + +export class MockBarGeometry { + private static readonly base: BarGeometry = { + x: 0, + y: 0, + width: 0, + height: 0, + color, + displayValue: { + text: '', + width: 0, + height: 0, + hideClippedValue: false, + isValueContainedInElement: false, + }, + seriesIdentifier: MockSeriesIdentifier.default(), + value: { + accessor: 'y0', + x: 0, + y: 0, + mark: null, + }, + seriesStyle: barSeriesStyle, + }; + + static default(partial?: RecursivePartial) { + return mergePartial(MockBarGeometry.base, partial, { mergeOptionalPartialValues: true }); + } + + static fromBaseline(baseline: RecursivePartial, omitKeys: string[] | string = []) { + return function(partial?: RecursivePartial) { + const geo = mergePartial(MockBarGeometry.base, partial, { mergeOptionalPartialValues: true }, [ + baseline, + ]); + return omit(geo, omitKeys); + }; + } +} + +export class MockLineGeometry { + private static readonly base: LineGeometry = { + line: '', + points: [], + color, + transform: { + x: 0, + y: 0, + }, + seriesIdentifier: MockSeriesIdentifier.default(), + seriesLineStyle: lineSeriesStyle.line, + seriesPointStyle: lineSeriesStyle.point, + clippedRanges: [], + }; + + static default(partial?: RecursivePartial) { + return mergePartial(MockLineGeometry.base, partial, { mergeOptionalPartialValues: true }); + } +} + +export class MockAreaGeometry { + private static readonly base: AreaGeometry = { + area: '', + lines: [], + points: [], + color, + transform: { + x: 0, + y: 0, + }, + seriesIdentifier: MockSeriesIdentifier.default(), + seriesAreaStyle: areaSeriesStyle.area, + seriesAreaLineStyle: areaSeriesStyle.line, + seriesPointStyle: areaSeriesStyle.point, + isStacked: false, + clippedRanges: [], + }; + + static default(partial?: RecursivePartial) { + return mergePartial(MockAreaGeometry.base, partial, { mergeOptionalPartialValues: true }); + } +} + +export class MockBubbleGeometry { + private static readonly base: BubbleGeometry = { + points: [], + color, + seriesIdentifier: MockSeriesIdentifier.default(), + seriesPointStyle: bubbleSeriesStyle.point, + }; + + static default(partial?: RecursivePartial) { + return mergePartial(MockBubbleGeometry.base, partial, { + mergeOptionalPartialValues: true, + }); + } +} diff --git a/src/mocks/hierarchical/dimension_codes.ts b/src/mocks/hierarchical/dimension_codes.ts index 17088569eb..39dafa659b 100644 --- a/src/mocks/hierarchical/dimension_codes.ts +++ b/src/mocks/hierarchical/dimension_codes.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -/** @internal */ export const productDimension = [ { sitc1: '0', name: 'Food and live animals' }, { sitc1: '1', name: 'Beverages and tobacco' }, @@ -30,7 +29,6 @@ export const productDimension = [ { sitc1: '9', name: 'Commodities and transactions not classified elsewhere' }, ]; -/** @internal */ export const regionDimension = [ { region: 'af', regionName: 'Africa' }, { region: 'as', regionName: 'Asia' }, @@ -40,7 +38,6 @@ export const regionDimension = [ { region: 'oc', regionName: 'Oceania' }, ]; -/** @internal */ export const countryDimension = [ { continentCountry: 'afago', country: 'ago', name: 'Angola' }, { continentCountry: 'afbdi', country: 'bdi', name: 'Burundi' }, diff --git a/src/mocks/hierarchical/index.ts b/src/mocks/hierarchical/index.ts index d8eeea86e4..0d19551e3e 100644 --- a/src/mocks/hierarchical/index.ts +++ b/src/mocks/hierarchical/index.ts @@ -21,7 +21,6 @@ import { sunburstMock } from './sunburst'; import { miniSunburstMock } from './mini_sunburst'; import { manyPieMock } from './many_pie'; -/** @internal */ export const mocks = { pie: pieMock, sunburst: sunburstMock, diff --git a/src/mocks/hierarchical/many_pie.ts b/src/mocks/hierarchical/many_pie.ts index a568bcef1e..03429ba501 100644 --- a/src/mocks/hierarchical/many_pie.ts +++ b/src/mocks/hierarchical/many_pie.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -/** @internal */ export const manyPieMock = [ { origin: 'chn', exportVal: 1680027842644 }, { origin: 'usa', exportVal: 1102566931395 }, diff --git a/src/mocks/hierarchical/mini_sunburst.ts b/src/mocks/hierarchical/mini_sunburst.ts index 7a9f8f3202..f9b40f3ca2 100644 --- a/src/mocks/hierarchical/mini_sunburst.ts +++ b/src/mocks/hierarchical/mini_sunburst.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -/** @internal */ export const miniSunburstMock = [ { sitc1: '7', dest: 'usa', exportVal: 553359100104 }, { sitc1: '7', dest: 'chn', exportVal: 392617281424 }, diff --git a/src/mocks/hierarchical/palettes.ts b/src/mocks/hierarchical/palettes.ts index 62c478476e..2050f86d8d 100644 --- a/src/mocks/hierarchical/palettes.ts +++ b/src/mocks/hierarchical/palettes.ts @@ -24,7 +24,6 @@ const CET2s: RgbTuple[] = [[46, 34, 235], [49, 32, 237], [52, 30, 238], [56, 29, // prettier-ignore const turbo: RgbTuple[] = [[48, 18, 59], [50, 21, 67], [51, 24, 74], [52, 27, 81], [53, 30, 88], [54, 33, 95], [55, 36, 102], [56, 39, 109], [57, 42, 115], [58, 45, 121], [59, 47, 128], [60, 50, 134], [61, 53, 139], [62, 56, 145], [63, 59, 151], [63, 62, 156], [64, 64, 162], [65, 67, 167], [65, 70, 172], [66, 73, 177], [66, 75, 181], [67, 78, 186], [68, 81, 191], [68, 84, 195], [68, 86, 199], [69, 89, 203], [69, 92, 207], [69, 94, 211], [70, 97, 214], [70, 100, 218], [70, 102, 221], [70, 105, 224], [70, 107, 227], [71, 110, 230], [71, 113, 233], [71, 115, 235], [71, 118, 238], [71, 120, 240], [71, 123, 242], [70, 125, 244], [70, 128, 246], [70, 130, 248], [70, 133, 250], [70, 135, 251], [69, 138, 252], [69, 140, 253], [68, 143, 254], [67, 145, 254], [66, 148, 255], [65, 150, 255], [64, 153, 255], [62, 155, 254], [61, 158, 254], [59, 160, 253], [58, 163, 252], [56, 165, 251], [55, 168, 250], [53, 171, 248], [51, 173, 247], [49, 175, 245], [47, 178, 244], [46, 180, 242], [44, 183, 240], [42, 185, 238], [40, 188, 235], [39, 190, 233], [37, 192, 231], [35, 195, 228], [34, 197, 226], [32, 199, 223], [31, 201, 221], [30, 203, 218], [28, 205, 216], [27, 208, 213], [26, 210, 210], [26, 212, 208], [25, 213, 205], [24, 215, 202], [24, 217, 200], [24, 219, 197], [24, 221, 194], [24, 222, 192], [24, 224, 189], [25, 226, 187], [25, 227, 185], [26, 228, 182], [28, 230, 180], [29, 231, 178], [31, 233, 175], [32, 234, 172], [34, 235, 170], [37, 236, 167], [39, 238, 164], [42, 239, 161], [44, 240, 158], [47, 241, 155], [50, 242, 152], [53, 243, 148], [56, 244, 145], [60, 245, 142], [63, 246, 138], [67, 247, 135], [70, 248, 132], [74, 248, 128], [78, 249, 125], [82, 250, 122], [85, 250, 118], [89, 251, 115], [93, 252, 111], [97, 252, 108], [101, 253, 105], [105, 253, 102], [109, 254, 98], [113, 254, 95], [117, 254, 92], [121, 254, 89], [125, 255, 86], [128, 255, 83], [132, 255, 81], [136, 255, 78], [139, 255, 75], [143, 255, 73], [146, 255, 71], [150, 254, 68], [153, 254, 66], [156, 254, 64], [159, 253, 63], [161, 253, 61], [164, 252, 60], [167, 252, 58], [169, 251, 57], [172, 251, 56], [175, 250, 55], [177, 249, 54], [180, 248, 54], [183, 247, 53], [185, 246, 53], [188, 245, 52], [190, 244, 52], [193, 243, 52], [195, 241, 52], [198, 240, 52], [200, 239, 52], [203, 237, 52], [205, 236, 52], [208, 234, 52], [210, 233, 53], [212, 231, 53], [215, 229, 53], [217, 228, 54], [219, 226, 54], [221, 224, 55], [223, 223, 55], [225, 221, 55], [227, 219, 56], [229, 217, 56], [231, 215, 57], [233, 213, 57], [235, 211, 57], [236, 209, 58], [238, 207, 58], [239, 205, 58], [241, 203, 58], [242, 201, 58], [244, 199, 58], [245, 197, 58], [246, 195, 58], [247, 193, 58], [248, 190, 57], [249, 188, 57], [250, 186, 57], [251, 184, 56], [251, 182, 55], [252, 179, 54], [252, 177, 54], [253, 174, 53], [253, 172, 52], [254, 169, 51], [254, 167, 50], [254, 164, 49], [254, 161, 48], [254, 158, 47], [254, 155, 45], [254, 153, 44], [254, 150, 43], [254, 147, 42], [254, 144, 41], [253, 141, 39], [253, 138, 38], [252, 135, 37], [252, 132, 35], [251, 129, 34], [251, 126, 33], [250, 123, 31], [249, 120, 30], [249, 117, 29], [248, 114, 28], [247, 111, 26], [246, 108, 25], [245, 105, 24], [244, 102, 23], [243, 99, 21], [242, 96, 20], [241, 93, 19], [240, 91, 18], [239, 88, 17], [237, 85, 16], [236, 83, 15], [235, 80, 14], [234, 78, 13], [232, 75, 12], [231, 73, 12], [229, 71, 11], [228, 69, 10], [226, 67, 10], [225, 65, 9], [223, 63, 8], [221, 61, 8], [220, 59, 7], [218, 57, 7], [216, 55, 6], [214, 53, 6], [212, 51, 5], [210, 49, 5], [208, 47, 5], [206, 45, 4], [204, 43, 4], [202, 42, 4], [200, 40, 3], [197, 38, 3], [195, 37, 3], [193, 35, 2], [190, 33, 2], [188, 32, 2], [185, 30, 2], [183, 29, 2], [180, 27, 1], [178, 26, 1], [175, 24, 1], [172, 23, 1], [169, 22, 1], [167, 20, 1], [164, 19, 1], [161, 18, 1], [158, 16, 1], [155, 15, 1], [152, 14, 1], [149, 13, 1], [146, 11, 1], [142, 10, 1], [139, 9, 2], [136, 8, 2], [133, 7, 2], [129, 6, 2], [126, 5, 2], [122, 4, 3]] -/** @internal */ export const palettes = { CET2s, turbo, diff --git a/src/mocks/hierarchical/pie.ts b/src/mocks/hierarchical/pie.ts index c21a3c9247..165ce10576 100644 --- a/src/mocks/hierarchical/pie.ts +++ b/src/mocks/hierarchical/pie.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -/** @internal */ export const pieMock = [ { sitc1: '7', exportVal: 3110253391368 }, { sitc1: '3', exportVal: 1929578418424 }, diff --git a/src/mocks/hierarchical/sunburst.ts b/src/mocks/hierarchical/sunburst.ts index 953d99ac13..85a8d2ee18 100644 --- a/src/mocks/hierarchical/sunburst.ts +++ b/src/mocks/hierarchical/sunburst.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -/** @internal */ export const sunburstMock = [ { sitc1: '7', dest: 'usa', exportVal: 553359100104 }, { sitc1: '7', dest: 'chn', exportVal: 392617281424 }, diff --git a/src/mocks/index.ts b/src/mocks/index.ts index b8a50aca7f..5311c06a27 100644 --- a/src/mocks/index.ts +++ b/src/mocks/index.ts @@ -17,4 +17,5 @@ * under the License. */ export * from './series'; +export * from './geometries'; export * from './theme'; diff --git a/src/mocks/scale/scale.ts b/src/mocks/scale/scale.ts index 8afacb72e5..7bb4cbc244 100644 --- a/src/mocks/scale/scale.ts +++ b/src/mocks/scale/scale.ts @@ -22,6 +22,7 @@ import { Scale, ScaleType } from '../../scales'; /** @internal */ export class MockScale { private static readonly base: Scale = { + scaleOrThrow: jest.fn().mockImplementation((x) => x), scale: jest.fn().mockImplementation((x) => x), type: ScaleType.Linear, bandwidth: 0, diff --git a/src/mocks/series/data.ts b/src/mocks/series/data.ts index fb0fe048a7..13feb2723e 100644 --- a/src/mocks/series/data.ts +++ b/src/mocks/series/data.ts @@ -26,6 +26,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: null, initialY1: null, initialY0: null, + mark: null, datum: { x: 0, y: null, @@ -37,6 +38,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: 0, initialY1: 3, initialY0: null, + mark: null, datum: { x: 1, y: 3, @@ -48,6 +50,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: 0, initialY1: 5, initialY0: null, + mark: null, datum: { x: 2, y: 5, @@ -59,6 +62,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: null, initialY1: null, initialY0: null, + mark: null, datum: { x: 3, y: null, @@ -70,6 +74,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: 0, initialY1: 4, initialY0: null, + mark: null, datum: { x: 4, y: 4, @@ -81,6 +86,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: null, initialY1: null, initialY0: null, + mark: null, datum: { x: 5, y: null, @@ -92,6 +98,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: 0, initialY1: 5, initialY0: null, + mark: null, datum: { x: 6, y: 5, @@ -103,6 +110,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: 0, initialY1: 6, initialY0: null, + mark: null, datum: { x: 7, y: 6, @@ -114,6 +122,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: null, initialY1: null, initialY0: null, + mark: null, datum: { x: 8, y: null, @@ -125,6 +134,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: null, initialY1: null, initialY0: null, + mark: null, datum: { x: 9, y: null, @@ -136,6 +146,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: null, initialY1: null, initialY0: null, + mark: null, datum: { x: 10, y: null, @@ -147,6 +158,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: 0, initialY1: 12, initialY0: null, + mark: null, datum: { x: 11, y: 12, @@ -158,6 +170,7 @@ export const fitFunctionData: DataSeriesDatum[] = [ y0: null, initialY1: null, initialY0: null, + mark: null, datum: { x: 12, y: null, diff --git a/src/mocks/series/series.ts b/src/mocks/series/series.ts index b3a83247dc..7bad6b1cd8 100644 --- a/src/mocks/series/series.ts +++ b/src/mocks/series/series.ts @@ -27,6 +27,16 @@ import { } from '../../chart_types/xy_chart/utils/series'; import { fitFunctionData } from './data'; import { FullDataSeriesDatum, WithIndex } from '../../chart_types/xy_chart/utils/fit_function'; +import { getRandomNumberGenerator } from '../utils'; + +const rng = getRandomNumberGenerator(); + +interface DomainRange { + min?: number; + max?: number; + fractionDigits?: number; + inclusive?: boolean; +} /** @internal */ export class MockDataSeries { @@ -57,7 +67,18 @@ export class MockDataSeries { }; } - static withData(data: DataSeries['data']): DataSeries { + static fromData(data: DataSeries['data']): DataSeries { + return { + ...MockDataSeries.base, + data, + }; + } + + static random( + options: { count?: number; x?: DomainRange; y?: DomainRange; mark?: DomainRange }, + includeMarks = false, + ): DataSeries { + const data = new Array(options?.count ?? 10).fill(0).map(() => MockDataSeriesDatum.random(options, includeMarks)); return { ...MockDataSeries.base, data, @@ -65,6 +86,10 @@ export class MockDataSeries { } } +type RawDataSeriesPartialData = Omit & { + data: Partial[]; +}; + /** @internal */ export class MockRawDataSeries { private static readonly base: RawDataSeries = { @@ -76,8 +101,23 @@ export class MockRawDataSeries { data: [], }; - static default(partial?: Partial) { - return mergePartial(MockRawDataSeries.base, partial); + static default({ data, ...partial }: Partial): RawDataSeries { + return { + ...MockRawDataSeries.base, + ...partial, + ...(data && { + data: data.map((datum) => MockRawDataSeriesDatum.default(datum)), + }), + }; + } + + static defaults(partials: Partial[], defaults?: Partial): RawDataSeries[] { + return partials.map((partial) => { + return MockRawDataSeries.default({ + ...defaults, + ...partial, + }); + }); } static fitFunction( @@ -95,11 +135,28 @@ export class MockRawDataSeries { }; } - static withData(data: RawDataSeries['data']): RawDataSeries { - return { + static fromData[] | Partial[][]>( + data: T, + defaults?: Partial>, + ): T extends Partial[] ? RawDataSeries : RawDataSeries[] { + const mergedDefault: RawDataSeries = { ...MockRawDataSeries.base, - data, + ...defaults, }; + + if (Array.isArray(data) && data[0] && Array.isArray(data[0])) { + return (data as Partial[][]).map((d, i) => ({ + ...mergedDefault, + specId: `spec${i + 1}`, + key: `key${i + 1}`, + data: d.map((datum) => MockRawDataSeriesDatum.default(datum)), + })) as any; + } + + return { + ...mergedDefault, + data: [MockRawDataSeriesDatum.default(data as any)], + } as any; } } @@ -108,10 +165,11 @@ export class MockDataSeriesDatum { private static readonly base: DataSeriesDatum = { x: 1, y1: 1, - y0: 1, - initialY1: 1, + y0: null, + mark: null, + initialY1: null, initialY0: 1, - datum: {}, + datum: null, }; static default(partial?: Partial): DataSeriesDatum { @@ -125,14 +183,21 @@ export class MockDataSeriesDatum { x, y1 = null, y0 = null, + mark = null, filled, }: Partial & Pick): DataSeriesDatum { return { x, y1, y0, + mark, initialY1: y1, initialY0: y0, + datum: { + x, + y1, + y0, + }, ...(filled && filled), }; } @@ -164,6 +229,24 @@ export class MockDataSeriesDatum { { mergeOptionalPartialValues: true }, ); } + + /** + * Psuedo-random values between a specified domain + * + * @param options + */ + static random( + options: { x?: DomainRange; y?: DomainRange; mark?: DomainRange }, + includeMark = false, + ): DataSeriesDatum { + return MockDataSeriesDatum.simple({ + x: rng(options?.x?.min, options?.x?.max, options.x?.fractionDigits, options.x?.inclusive), + y1: rng(options?.y?.min, options?.y?.max, options.y?.fractionDigits, options.y?.inclusive), + ...(includeMark && { + mark: rng(options?.mark?.min, options?.mark?.max, options.mark?.fractionDigits, options.mark?.inclusive), + }), + }); + } } /** @internal */ @@ -171,8 +254,13 @@ export class MockRawDataSeriesDatum { private static readonly base: RawDataSeriesDatum = { x: 1, y1: 1, - y0: 1, - datum: {}, + y0: null, + mark: null, + datum: { + x: 1, + y1: 1, + y0: 1, + }, }; static default(partial?: Partial): RawDataSeriesDatum { @@ -191,6 +279,12 @@ export class MockRawDataSeriesDatum { x, y1, y0, + mark: null, + datum: { + x: 1, + y1: 1, + y0: 1, + }, }; } diff --git a/src/mocks/specs/specs.ts b/src/mocks/specs/specs.ts index 61c9ce515a..06d830f329 100644 --- a/src/mocks/specs/specs.ts +++ b/src/mocks/specs/specs.ts @@ -27,6 +27,7 @@ import { LineSeriesSpec, BasicSeriesSpec, SeriesTypes, + BubbleSeriesSpec, } from '../../chart_types/xy_chart/utils/specs'; import { ScaleType } from '../../scales'; import { ChartTypes } from '../../chart_types'; @@ -106,6 +107,21 @@ export class MockSeriesSpec { data: [], }; + private static readonly bubbleBase: BubbleSeriesSpec = { + chartType: ChartTypes.XYAxis, + specType: SpecTypes.Series, + id: 'spec1', + seriesType: SeriesTypes.Bubble, + groupId: DEFAULT_GLOBAL_ID, + xScaleType: ScaleType.Ordinal, + yScaleType: ScaleType.Linear, + xAccessor: 'x', + yAccessors: ['y'], + yScaleToDataExtent: false, + hideInLegend: false, + data: [], + }; + private static readonly sunburstBase: PartitionSpec = { chartType: ChartTypes.Partition, specType: SpecTypes.Series, @@ -192,13 +208,15 @@ export class MockSeriesSpec { }); } - static byType(type?: 'line' | 'bar' | 'area'): BasicSeriesSpec { + static byType(type?: SeriesTypes): BasicSeriesSpec { switch (type) { - case 'line': + case SeriesTypes.Line: return MockSeriesSpec.lineBase; - case 'area': + case SeriesTypes.Area: return MockSeriesSpec.areaBase; - case 'bar': + case SeriesTypes.Bubble: + return MockSeriesSpec.bubbleBase; + case SeriesTypes.Bar: default: return MockSeriesSpec.barBase; } diff --git a/src/mocks/utils.ts b/src/mocks/utils.ts index 7441a55cb2..6ffaa8d964 100644 --- a/src/mocks/utils.ts +++ b/src/mocks/utils.ts @@ -31,7 +31,38 @@ export const forcedType = (obj: Partial): T => { return obj as T; }; -export const getRandomNumberGenerator = (seed = process.env.RNG_SEED) => seedrandom(seed); +export type RandomNumberGenerator = ( + min?: number, + max?: number, + fractionDigits?: number, + inclusive?: boolean, +) => number; + +/** + * Return rng function with optional `min`, `max` and `fractionDigits` params + * + * @param string process.env.RNG_SEED + */ +export const getRandomNumberGenerator = (seed = process.env.RNG_SEED): RandomNumberGenerator => { + const rng = seedrandom(seed); + + /** + * Random number generator + * + * @param {} min=0 + * @param {} max=1 + * @param {} fractionDigits=0 + */ + return function randomNumberGenerator(min = 0, max = 1, fractionDigits = 0, inclusive = true) { + const precision = Math.pow(10, Math.max(fractionDigits, 0)); + const scaledMax = max * precision; + const scaledMin = min * precision; + const offset = inclusive ? 1 : 0; + const num = Math.floor(rng() * (scaledMax - scaledMin + offset)) + scaledMin; + + return num / precision; + }; +}; export class SeededDataGenerator extends DataGenerator { constructor(frequency = 500) { diff --git a/src/renderers/canvas/index.ts b/src/renderers/canvas/index.ts index 53576a82b9..e148b3f0b2 100644 --- a/src/renderers/canvas/index.ts +++ b/src/renderers/canvas/index.ts @@ -60,14 +60,17 @@ export function renderLayers(ctx: CanvasRenderingContext2D, layers: Array<(ctx: /** @internal */ export function withClip( ctx: CanvasRenderingContext2D, - clip: { x: number; y: number; width: number; height: number }, + clipppings: Rect, fun: (ctx: CanvasRenderingContext2D) => void, + shouldClip = true, ) { withContext(ctx, (ctx) => { - const { x, y, width, height } = clip; - ctx.beginPath(); - ctx.rect(x, y, width, height); - ctx.clip(); + if (shouldClip) { + const { x, y, width, height } = clipppings; + ctx.beginPath(); + ctx.rect(x, y, width, height); + ctx.clip(); + } withContext(ctx, (ctx) => { fun(ctx); }); diff --git a/src/scales/index.ts b/src/scales/index.ts index 19b72c7727..12c30a5626 100644 --- a/src/scales/index.ts +++ b/src/scales/index.ts @@ -18,6 +18,8 @@ import { $Values } from 'utility-types'; +import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup'; + /** * A `Scale` interface. A scale can map an input value within a specified domain * to an output value from a specified range. @@ -28,8 +30,9 @@ export interface Scale { domain: any[]; range: number[]; ticks: () => any[]; - scale: (value: string | number) => number; - pureScale: (value: any) => number; + scaleOrThrow(value?: PrimitiveValue): number; + scale: (value?: PrimitiveValue) => number | null; + pureScale: (value?: PrimitiveValue) => number | null; invert: (value: number) => any; invertWithStep: ( value: number, diff --git a/src/scales/scale_band.test.ts b/src/scales/scale_band.test.ts index 0a1e23b578..c670076377 100644 --- a/src/scales/scale_band.test.ts +++ b/src/scales/scale_band.test.ts @@ -79,11 +79,11 @@ describe('Scale Band', () => { expect(scale.scale('c')).toBe(25); expect(scale.scale('d')).toBe(0); }); - it('shall return undefined for out of domain values', () => { + it('shall return null for out of domain values', () => { const scale = new ScaleBand(['a', 'b', 'c', 'd'], [0, 100]); - expect(scale.scale('e')).toBeUndefined(); - expect(scale.scale(0)).toBeUndefined(); - expect(scale.scale(null)).toBeUndefined(); + expect(scale.scale('e')).toBeNull(); + expect(scale.scale(0)).toBeNull(); + expect(scale.scale(null)).toBeNull(); }); it('shall scale a numeric domain with padding', () => { const scale = new ScaleBand([0, 1, 2], [0, 120], undefined, 0.5); @@ -107,8 +107,8 @@ describe('Scale Band', () => { }); it('shall not scale scale null values', () => { const scale = new ScaleBand([0, 1, 2], [0, 120], undefined, 0.5); - expect(scale.scale(-1)).toBeUndefined(); - expect(scale.scale(3)).toBeUndefined(); + expect(scale.scale(-1)).toBeNull(); + expect(scale.scale(3)).toBeNull(); }); it('shall invert all values in range', () => { const domain = ['a', 'b', 'c', 'd']; @@ -133,4 +133,16 @@ describe('Scale Band', () => { expect(scale.isSingleValue()).toBe(false); }); }); + + describe('#scaleOrThrow', () => { + const scale = new ScaleBand(['a', 'b'], [0, 100]); + + it('should NOT throw for values in domain', () => { + expect(() => scale.scaleOrThrow('a')).not.toThrow(); + }); + + it('should throw for values not in domain', () => { + expect(() => scale.scaleOrThrow('c')).toThrow(); + }); + }); }); diff --git a/src/scales/scale_band.ts b/src/scales/scale_band.ts index 953fe74405..ed8efa41e1 100644 --- a/src/scales/scale_band.ts +++ b/src/scales/scale_band.ts @@ -16,9 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { scaleBand, scaleQuantize, ScaleQuantize } from 'd3-scale'; +import { scaleBand, scaleQuantize, ScaleQuantize, ScaleBand as D3ScaleBand } from 'd3-scale'; + import { clamp } from '../utils/commons'; import { ScaleType, Scale } from '.'; +import { stringifyNullsUndefined } from '../chart_types/xy_chart/state/utils'; +import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup'; + /** * Categorical scale * @internal @@ -34,7 +38,7 @@ export class ScaleBand implements Scale { readonly invertedScale: ScaleQuantize; readonly minInterval: number; readonly barsPadding: number; - private readonly d3Scale: any; + private readonly d3Scale: D3ScaleBand>; constructor( domain: any[], @@ -48,7 +52,7 @@ export class ScaleBand implements Scale { barsPadding = 0, ) { this.type = ScaleType.Ordinal; - this.d3Scale = scaleBand(); + this.d3Scale = scaleBand>(); this.d3Scale.domain(domain); this.d3Scale.range(range); const safeBarPadding = clamp(barsPadding, 0, 1); @@ -63,7 +67,7 @@ export class ScaleBand implements Scale { this.bandwidth = overrideBandwidth * (1 - safeBarPadding); } this.bandwidthPadding = this.bandwidth; - // TO FIX: we are assiming that it's ordered + // TO FIX: we are assuming that it's ordered this.isInverted = this.domain[0] > this.domain[1]; this.invertedScale = scaleQuantize() .domain(range) @@ -71,28 +75,53 @@ export class ScaleBand implements Scale { this.minInterval = 0; } - scale(value: any) { - return this.d3Scale(value); + private getScaledValue(value?: PrimitiveValue): number | null { + const scaleValue = this.d3Scale(stringifyNullsUndefined(value)); + + if (scaleValue === undefined || isNaN(scaleValue)) { + return null; + } + + return scaleValue; } - pureScale(value: any) { - return this.d3Scale(value); + + scaleOrThrow(value?: PrimitiveValue): number { + const scaleValue = this.scale(value); + + if (scaleValue === null) { + throw new Error(`Unable to scale value: ${scaleValue})`); + } + + return scaleValue; + } + + scale(value?: PrimitiveValue) { + return this.getScaledValue(value); + } + + pureScale(value?: PrimitiveValue) { + return this.getScaledValue(value); } ticks() { return this.domain; } + invert(value: any) { return this.invertedScale(value); } + invertWithStep(value: any) { return { value: this.invertedScale(value), withinBandwidth: true, }; } + isSingleValue() { return this.domain.length < 2; } + isValueInDomain(value: any) { return this.domain.includes(value); } diff --git a/src/scales/scale_continuous.test.ts b/src/scales/scale_continuous.test.ts index dcf776d401..8501da4c21 100644 --- a/src/scales/scale_continuous.test.ts +++ b/src/scales/scale_continuous.test.ts @@ -446,4 +446,30 @@ describe('Scale Continuous', () => { expect(scale.getTicks(10, false)).toEqual([0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7]); }); }); + + describe('#scaleOrThrow', () => { + const scale = new ScaleContinuous({ type: ScaleType.Linear, domain: [0, 100], range: [0, 100] }); + + it('should NOT throw for values in domain', () => { + expect(() => scale.scaleOrThrow(10)).not.toThrow(); + }); + + it('should throw for NaN values', () => { + // @ts-ignore + jest.spyOn(scale, 'd3Scale').mockImplementationOnce(() => NaN); + expect(() => scale.scaleOrThrow(1)).toThrow(); + }); + + it('should throw for string values', () => { + expect(() => scale.scaleOrThrow('c')).toThrow(); + }); + + it('should throw for null values', () => { + expect(() => scale.scaleOrThrow(null)).toThrow(); + }); + + it('should throw for undefined values', () => { + expect(() => scale.scaleOrThrow(undefined)).toThrow(); + }); + }); }); diff --git a/src/scales/scale_continuous.ts b/src/scales/scale_continuous.ts index ad83ffa626..af8555b2bd 100644 --- a/src/scales/scale_continuous.ts +++ b/src/scales/scale_continuous.ts @@ -31,16 +31,17 @@ import { import { clamp, mergePartial } from '../utils/commons'; import { ScaleContinuousType, ScaleType, Scale } from '.'; import { getMomentWithTz } from '../utils/data/date_time'; +import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup'; /** * d3 scales excluding time scale */ -type D3ScaleNonTime = ScaleLinear | ScaleLogarithmic | ScalePower; +type D3ScaleNonTime = ScaleLinear | ScaleLogarithmic | ScalePower; /** * All possible d3 scales */ -type D3Scale = D3ScaleNonTime | ScaleTime; +type D3Scale = D3ScaleNonTime | ScaleTime; const SCALES = { [ScaleType.Linear]: scaleLinear, @@ -236,7 +237,20 @@ export class ScaleContinuous implements Scale { } } } + + private getScaledValue(value?: PrimitiveValue): number | null { + if (typeof value !== 'number' || isNaN(value)) { + return null; + } + + const scaledValue = this.d3Scale(value); + + return isNaN(scaledValue) ? null : scaledValue; + } + getTicks(ticks: number, integersOnly: boolean) { + // TODO: cleanup types for ticks btw time and non-time scales + // This is forcing a return type of number[] but is really (number|Date)[] return integersOnly ? (this.d3Scale as D3ScaleNonTime) .ticks(ticks) @@ -244,18 +258,39 @@ export class ScaleContinuous implements Scale { .map((item: number) => parseInt(item.toFixed(0))) : (this.d3Scale as D3ScaleNonTime).ticks(ticks); } - scale(value: any) { - return this.d3Scale(value) + (this.bandwidthPadding / 2) * this.totalBarsInCluster; + + scaleOrThrow(value?: PrimitiveValue): number { + const scaleValue = this.scale(value); + + if (scaleValue === null) { + throw new Error(`Unable to scale value: ${scaleValue})`); + } + + return scaleValue; + } + + scale(value?: PrimitiveValue) { + const scaledValue = this.getScaledValue(value); + + return scaledValue === null ? null : scaledValue + (this.bandwidthPadding / 2) * this.totalBarsInCluster; } - pureScale(value: any) { + + pureScale(value?: PrimitiveValue) { if (this.bandwidth === 0) { - return this.d3Scale(value); + return this.getScaledValue(value); + } + + if (typeof value !== 'number' || isNaN(value)) { + return null; } - return this.d3Scale(value + this.minInterval / 2); + + return this.getScaledValue(value + this.minInterval / 2); } + ticks() { return this.tickValues; } + invert(value: number): number { let invertedValue = this.d3Scale.invert(value); if (this.type === ScaleType.Time) { @@ -264,6 +299,7 @@ export class ScaleContinuous implements Scale { return invertedValue as number; } + invertWithStep( value: number, data: number[], @@ -312,6 +348,7 @@ export class ScaleContinuous implements Scale { withinBandwidth: false, }; } + isSingleValue() { if (this.isSingleValueHistogram) { return true; @@ -324,6 +361,7 @@ export class ScaleContinuous implements Scale { const max = this.domain[this.domain.length - 1]; return max === min; } + isValueInDomain(value: number) { return value >= this.domain[0] && value <= this.domain[1]; } diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx index d51dd20643..69ba82dfbf 100644 --- a/src/specs/settings.tsx +++ b/src/specs/settings.tsx @@ -110,6 +110,10 @@ export interface TooltipValue { * The value to display */ value: any; + /** + * The mark value to display + */ + markValue?: any; /** * The color of the graphic mark (by default the color of the series) */ @@ -165,6 +169,11 @@ export interface LegendColorPickerProps { } export type LegendColorPicker = ComponentType; +/** + * Buffer between cursor and point to trigger interaction + */ +export type MarkBuffer = number | ((radius: number) => number); + export interface SettingsSpec extends Spec { /** * Partial theme to be merged with base @@ -213,6 +222,7 @@ export interface SettingsSpec extends Spec { onElementClick?: ElementClickListener; onElementOver?: ElementOverListener; onElementOut?: BasicListener; + pointBuffer?: MarkBuffer; onBrushEnd?: BrushEndListener; onLegendItemOver?: LegendItemListener; onLegendItemOut?: BasicListener; diff --git a/src/utils/__mocks__/commons.ts b/src/utils/__mocks__/commons.ts index 7186993cba..7d43635644 100644 --- a/src/utils/__mocks__/commons.ts +++ b/src/utils/__mocks__/commons.ts @@ -18,6 +18,9 @@ const module = jest.requireActual('../commons.ts'); +export const ColorVariant = module.ColorVariant; +export const Position = module.Position; + export const identity = jest.fn(module.identity); export const compareByValueAsc = jest.fn(module.compareByValueAsc); export const clamp = jest.fn(module.clamp); diff --git a/src/utils/accessor.ts b/src/utils/accessor.ts index 7158443d1b..e445704c33 100644 --- a/src/utils/accessor.ts +++ b/src/utils/accessor.ts @@ -18,8 +18,8 @@ import { Datum } from './commons'; -type UnaryAccessorFn = (datum: Datum) => any; -type BinaryAccessorFn = (datum: Datum, index: number) => any; +type UnaryAccessorFn = (datum: Datum) => Return; +type BinaryAccessorFn = (datum: Datum, index: number) => Return; export type AccessorFn = UnaryAccessorFn; export type IndexedAccessorFn = UnaryAccessorFn | BinaryAccessorFn; diff --git a/src/utils/commons.test.ts b/src/utils/commons.test.ts index e17d1b7ee5..da18239f02 100644 --- a/src/utils/commons.test.ts +++ b/src/utils/commons.test.ts @@ -54,9 +54,8 @@ describe('commons utilities', () => { }); test('compareByValueAsc', () => { - expect(compareByValueAsc(10, 20)).toBeLessThan(0); - expect(compareByValueAsc(20, 10)).toBeGreaterThan(0); - expect(compareByValueAsc(10, 10)).toBe(0); + expect([2, 1, 4, 3].sort(compareByValueAsc)).toEqual([1, 2, 3, 4]); + expect(['b', 'a', 'd', 'c'].sort(compareByValueAsc)).toEqual(['a', 'b', 'c', 'd']); }); describe('getPartialValue', () => { @@ -181,16 +180,31 @@ describe('commons utilities', () => { }); describe('hasPartialObjectToMerge', () => { + it('should return false if base is null', () => { + const result = hasPartialObjectToMerge(null); + expect(result).toBe(false); + }); + it('should return false if base is an array', () => { const result = hasPartialObjectToMerge([]); expect(result).toBe(false); }); + it('should return false if base is a Set', () => { + const result = hasPartialObjectToMerge(new Set()); + expect(result).toBe(false); + }); + it('should return true if base and partial are objects', () => { const result = hasPartialObjectToMerge({}, {}); expect(result).toBe(true); }); + it('should return false if base is object and patial is null', () => { + const result = hasPartialObjectToMerge({}, null as any); + expect(result).toBe(false); + }); + it('should return true if base and any additionalPartials are objects', () => { const result = hasPartialObjectToMerge({}, undefined, ['string', [], {}]); expect(result).toBe(true); @@ -439,6 +453,357 @@ describe('commons utilities', () => { expect(base).toEqual(baseClone); }); + describe('Maps', () => { + it('should merge top-level Maps', () => { + const result = mergePartial( + new Map([ + [ + 'a', + { + name: 'Nick', + }, + ], + [ + 'b', + { + name: 'Marco', + }, + ], + ]), + new Map([ + [ + 'b', + { + name: 'rachel', + }, + ], + ]), + { + mergeMaps: true, + }, + ); + expect(result).toEqual( + new Map([ + [ + 'a', + { + name: 'Nick', + }, + ], + [ + 'b', + { + name: 'rachel', + }, + ], + ]), + ); + }); + + it('should merge nested Maps', () => { + const result = mergePartial( + { + test: new Map([ + [ + 'cat', + { + name: 'cat', + }, + ], + ]), + }, + { + test: new Map([ + [ + 'dog', + { + name: 'dog', + }, + ], + ]), + }, + { + mergeMaps: true, + mergeOptionalPartialValues: true, + }, + ); + expect(result).toEqual({ + test: new Map([ + [ + 'cat', + { + name: 'cat', + }, + ], + [ + 'dog', + { + name: 'dog', + }, + ], + ]), + }); + }); + + it('should merge nested Maps', () => { + const result = mergePartial( + { + test: new Map([ + [ + 'cat', + { + name: 'toby', + }, + ], + ]), + }, + { + test: new Map([ + [ + 'cat', + { + name: 'snickers', + }, + ], + ]), + }, + { + mergeMaps: true, + }, + ); + expect(result).toEqual({ + test: new Map([ + [ + 'cat', + { + name: 'snickers', + }, + ], + ]), + }); + }); + + it('should merge nested Maps with mergeOptionalPartialValues', () => { + const result = mergePartial( + { + test: new Map([ + [ + 'cat', + { + name: 'toby', + }, + ], + ]), + }, + { + test: new Map([ + [ + 'dog', + { + name: 'lucky', + }, + ], + ]), + }, + { + mergeMaps: true, + mergeOptionalPartialValues: true, + }, + ); + expect(result).toEqual({ + test: new Map([ + [ + 'cat', + { + name: 'toby', + }, + ], + [ + 'dog', + { + name: 'lucky', + }, + ], + ]), + }); + }); + + it('should merge nested Maps from additionalPartials', () => { + const result = mergePartial( + { + test: new Map([ + [ + 'cat', + { + name: 'toby', + }, + ], + ]), + }, + undefined, + { + mergeMaps: true, + }, + [ + { + test: new Map([ + [ + 'cat', + { + name: 'snickers', + }, + ], + ]), + }, + ], + ); + expect(result).toEqual({ + test: new Map([ + [ + 'cat', + { + name: 'toby', + }, + ], + [ + 'cat', + { + name: 'snickers', + }, + ], + ]), + }); + }); + + it('should replace Maps when mergeMaps is false', () => { + const result = mergePartial( + { + test: new Map([ + [ + 'cat', + { + name: 'toby', + }, + ], + ]), + }, + { + test: new Map([ + [ + 'dog', + { + name: 'snickers', + }, + ], + ]), + }, + ); + expect(result).toEqual({ + test: new Map([ + [ + 'dog', + { + name: 'snickers', + }, + ], + ]), + }); + }); + + it('should replace Maps when mergeMaps is false from additionalPartials', () => { + const result = mergePartial( + { + test: new Map([ + [ + 'cat', + { + name: 'toby', + }, + ], + ]), + }, + undefined, + undefined, + [ + { + test: new Map([ + [ + 'dog', + { + name: 'snickers', + }, + ], + ]), + }, + ], + ); + expect(result).toEqual({ + test: new Map([ + [ + 'dog', + { + name: 'snickers', + }, + ], + ]), + }); + }); + }); + + describe('Sets', () => { + it('should merge Sets like arrays', () => { + const result = mergePartial( + { + animals: new Set(['cat', 'dog']), + }, + { + animals: new Set(['cat', 'dog', 'bird']), + }, + ); + expect(result).toEqual({ + animals: new Set(['cat', 'dog', 'bird']), + }); + }); + + it('should merge Sets like arrays with mergeOptionalPartialValues', () => { + interface Test { + animals: Set; + numbers?: Set; + } + const result = mergePartial( + { + animals: new Set(['cat', 'dog']), + }, + { + numbers: new Set([1, 2, 3]), + }, + { mergeOptionalPartialValues: true }, + ); + expect(result).toEqual({ + animals: new Set(['cat', 'dog']), + numbers: new Set([1, 2, 3]), + }); + }); + + it('should merge Sets like arrays from additionalPartials', () => { + const result = mergePartial( + { + animals: new Set(['cat', 'dog']), + }, + {}, + {}, + [ + { + animals: new Set(['cat', 'dog', 'bird']), + }, + ], + ); + expect(result).toEqual({ + animals: new Set(['cat', 'dog', 'bird']), + }); + }); + }); + describe('additionalPartials', () => { test('should override string value in base with first partial value', () => { const partial: PartialTestType = { string: 'test1' }; diff --git a/src/utils/commons.ts b/src/utils/commons.ts index c86191d259..48821d6e1f 100644 --- a/src/utils/commons.ts +++ b/src/utils/commons.ts @@ -57,8 +57,8 @@ export function identity(value: T): T { } /** @internal */ -export function compareByValueAsc(firstEl: number, secondEl: number): number { - return firstEl - secondEl; +export function compareByValueAsc(a: number | string, b: number | string): number { + return a > b ? 1 : -1; } /** @internal */ @@ -117,15 +117,40 @@ export function htmlIdGenerator(idPrefix?: string) { * ``` */ export type RecursivePartial = { - [P in keyof T]?: T[P] extends (infer U)[] + [P in keyof T]?: T[P] extends NonAny[] // checks for nested any[] + ? T[P] + : T[P] extends ReadonlyArray // checks for nested ReadonlyArray + ? T[P] + : T[P] extends (infer U)[] ? RecursivePartial[] : T[P] extends ReadonlyArray // eslint-disable-line @typescript-eslint/array-type ? ReadonlyArray> // eslint-disable-line @typescript-eslint/array-type - : RecursivePartial; + : T[P] extends Set // checks for Sets + ? Set> + : T[P] extends Map // checks for Maps + ? Map> + : T[P] extends NonAny // checks for primative values + ? T[P] + : RecursivePartial; // recurse for all non-array and non-primative values }; +type NonAny = number | boolean | string | symbol | null; export interface MergeOptions { + /** + * Includes all available keys of every provided partial at a given level. + * This is opposite to normal behavoir, which only uses keys from the base + * object to merge values. + * + * @default false + */ mergeOptionalPartialValues?: boolean; + /** + * Merges Maps same as objects. By default this is disabled and Maps are replaced on the base + * with a defined Map on any partial. + * + * @default false + */ + mergeMaps?: boolean; } /** @internal */ @@ -141,13 +166,21 @@ export function getPartialValue(base: T, partial?: RecursivePartial, parti * @internal */ export function getAllKeys(object: any, objects: any[] = []): string[] { + const initalKeys = object instanceof Map ? [...object.keys()] : Object.keys(object); + return objects.reduce((keys: any[], obj) => { if (obj && typeof obj === 'object') { - keys.push(...Object.keys(obj)); + const newKeys = obj instanceof Map ? obj.keys() : Object.keys(obj); + keys.push(...newKeys); } return keys; - }, Object.keys(object)); + }, initalKeys); +} + +/** @internal */ +export function isArrayOrSet(value: any): boolean { + return Array.isArray(value) || value instanceof Set; } /** @internal */ @@ -156,12 +189,12 @@ export function hasPartialObjectToMerge( partial?: RecursivePartial, additionalPartials: RecursivePartial[] = [], ): boolean { - if (Array.isArray(base)) { + if (isArrayOrSet(base)) { return false; } - if (typeof base === 'object') { - if (typeof partial === 'object' && !Array.isArray(partial)) { + if (typeof base === 'object' && base !== null) { + if (typeof partial === 'object' && !isArrayOrSet(partial) && partial !== null) { return true; } @@ -177,7 +210,15 @@ export function shallowClone(value: any) { return [...value]; } + if (value instanceof Set) { + return new Set([...value]); + } + if (typeof value === 'object' && value !== null) { + if (value instanceof Map) { + return new Map(value.entries()); + } + return { ...value }; } @@ -203,9 +244,19 @@ export function mergePartial( const baseClone = shallowClone(base); if (hasPartialObjectToMerge(base, partial, additionalPartials)) { - if (partial !== undefined && options.mergeOptionalPartialValues) { + const mapCondition = !(baseClone instanceof Map) || options.mergeMaps; + if (partial !== undefined && options.mergeOptionalPartialValues && mapCondition) { getAllKeys(partial, additionalPartials).forEach((key) => { - if (!(key in baseClone)) { + if (baseClone instanceof Map) { + if (!baseClone.has(key)) { + baseClone.set( + key, + (partial as any).get(key) !== undefined + ? (partial as any).get(key) + : additionalPartials.find((v: any) => v.get(key) !== undefined) || new Map().get(key), + ); + } + } else if (!(key in baseClone)) { (baseClone as any)[key] = (partial as any)[key] !== undefined ? (partial as any)[key] @@ -214,6 +265,33 @@ export function mergePartial( }); } + if (baseClone instanceof Map) { + if (options.mergeMaps) { + return [...baseClone.keys()].reduce((newBase: Map, key) => { + const partialValue = partial && (partial as any).get(key); + const partialValues = additionalPartials.map((v) => + typeof v === 'object' && v instanceof Map ? v.get(key) : undefined, + ); + const baseValue = (base as any).get(key); + + newBase.set(key, mergePartial(baseValue, partialValue, options, partialValues)); + + return newBase; + }, baseClone as any); + } + + if (partial !== undefined) { + return partial as any; + } + + const additional = additionalPartials.find((p: any) => p !== undefined); + if (additional) { + return additional as any; + } + + return baseClone as any; + } + return Object.keys(base).reduce((newBase, key) => { const partialValue = partial && (partial as any)[key]; const partialValues = additionalPartials.map((v) => (typeof v === 'object' ? (v as any)[key] : undefined)); diff --git a/src/utils/d3-delaunay/index.ts b/src/utils/d3-delaunay/index.ts new file mode 100644 index 0000000000..f4cd87770a --- /dev/null +++ b/src/utils/d3-delaunay/index.ts @@ -0,0 +1,1545 @@ +/* eslint-disable file-header/file-header */ + +/** + * @notice + * This product includes code that is adapted d3-delaunay@5.2.1, + * which is available under a "ISC" license. + * + * Copyright 2018 Observable, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright notice + * and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +// @ts-nocheck + +/** + * Delaunay triangulation + */ +interface DelaunayI

{ + /** + * The coordinates of the points as an array [x0, y0, x1, y1, ...]. + * Typically, this is a Float64Array, however you can use any array-like type in the constructor. + */ + points: ArrayLike; + + /** + * The halfedge indices as an Int32Array [j0, j1, ...]. + * For each index 0 <= i < halfedges.length, there is a halfedge from triangle vertex j = halfedges[i] to triangle vertex i. + */ + halfedges: Int32Array; + + /** + * An arbitrary node on the convex hull. + * The convex hull is represented as a circular doubly-linked list of nodes. + */ + hull: Node; + + /** + * The triangle vertex indices as an Uint32Array [i0, j0, k0, i1, j1, k1, ...]. + * Each contiguous triplet of indices i, j, k forms a counterclockwise triangle. + * The coordinates of the triangle's points can be found by going through 'points'. + */ + triangles: Uint32Array; + + /** + * The incoming halfedge indexes as a Int32Array [e0, e1, e2, ...]. + * For each point i, inedges[i] is the halfedge index e of an incoming halfedge. + * For coincident points, the halfedge index is -1; for points on the convex hull, the incoming halfedge is on the convex hull; for other points, the choice of incoming halfedge is arbitrary. + */ + inedges: Int32Array; + + /** + * The outgoing halfedge indexes as a Int32Array [e0, e1, e2, ...]. + * For each point i on the convex hull, outedges[i] is the halfedge index e of the corresponding outgoing halfedge; for other points, the halfedge index is -1. + */ + outedges: Int32Array; + + /** + * Returns the index of the input point that is closest to the specified point ⟨x, y⟩. + * The search is started at the specified point i. If i is not specified, it defaults to zero. + */ + find(x: number, y: number, i?: number): number; + + /** + * Returns an iterable over the indexes of the neighboring points to the specified point i. + * The iterable is empty if i is a coincident point. + */ + neighbors(i: number): IterableIterator; + + /** + * Returns the closed polygon [[x0, y0], [x1, y1], ..., [x0, y0]] representing the convex hull. + */ + hullPolygon(): Polygon; + + /** + * Returns the closed polygon [[x0, y0], [x1, y1], [x2, y2], [x0, y0]] representing the triangle i. + */ + trianglePolygon(i: number): Triangle; + + /** + * Returns an iterable over the polygons for each triangle, in order. + */ + trianglePolygons(): IterableIterator; + + /** + * Returns the Voronoi diagram for the associated points. + * When rendering, the diagram will be clipped to the specified bounds = [xmin, ymin, xmax, ymax]. + * If bounds is not specified, it defaults to [0, 0, 960, 500]. + * See To Infinity and Back Again for an interactive explanation of Voronoi cell clipping. + */ + voronoi(bounds?: Bounds): Voronoi

; +} + +/** + * A point represented as an array tuple [x, y]. + */ +type Point = number[]; + +/** + * A closed polygon [[x0, y0], [x1, y1], [x2, y2], [x0, y0]] representing a triangle. + */ +type Triangle = Point[]; + +/** + * A closed polygon [[x0, y0], [x1, y1], ..., [x0, y0]]. + */ +type PolygonI = Point[]; + +/** + * A rectangular area [x, y, width, height]. + */ +export type Bounds = number[]; + +/** + * A function to extract a x- or y-coordinate from the specified point. + */ +type GetCoordinate = (point: P, i: number, points: PS) => number; + +/** + * A point node on a convex hull (represented as a circular linked list). + */ +interface Node { + /** + * The index of the associated point. + */ + i: number; + + /** + * The x-coordinate of the associated point. + */ + x: number; + + /** + * The y-coordinate of the associated point. + */ + y: number; + + /** + * The index of the (incoming or outgoing?) associated halfedge. + */ + t: number; + + /** + * The previous node on the hull. + */ + prev: Node; + + /** + * The next node on the hull. + */ + next: Node; + + /** + * Whether the node has been removed from the linked list. + */ + removed: boolean; +} + +/** + * An interface for the rect() method of the CanvasPathMethods API. + */ +interface RectContext { + /** + * rect() method of the CanvasPathMethods API. + */ + rect(x: number, y: number, width: number, height: number): void; +} + +/** + * An interface for the moveTo() method of the CanvasPathMethods API. + */ +interface MoveContext { + /** + * moveTo() method of the CanvasPathMethods API. + */ + moveTo(x: number, y: number): void; +} + +/** + * An interface for the lineTo() method of the CanvasPathMethods API. + */ +interface LineContext { + /** + * lineTo() method of the CanvasPathMethods API. + */ + lineTo(x: number, y: number): void; +} + +/** + * An interface for the arc() method of the CanvasPathMethods API. + */ +interface ArcContext { + /** + * arc() method of the CanvasPathMethods API. + */ + arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void; +} + +/** + * An interface for the closePath() method of the CanvasPathMethods API. + */ +interface ClosableContext { + /** + * closePath() method of the CanvasPathMethods API. + */ + closePath(): void; +} + +/** + * Voronoi regions + */ +interface VoronoiI

{ + /** + * The Voronoi diagram’s associated Delaunay triangulation. + */ + delaunay: DelaunayI

; + + /** + * The circumcenters of the Delaunay triangles [cx0, cy0, cx1, cy1, ...]. + * Each contiguous pair of coordinates cx, cy is the circumcenter for the corresponding triangle. + * These circumcenters form the coordinates of the Voronoi cell polygons. + */ + circumcenters: Float64Array; + + /** + * An array [vx0, vy0, wx0, wy0, ...] where each non-zero quadruple describes an open (infinite) cell + * on the outer hull, giving the directions of two open half-lines. + */ + vectors: Float64Array; + + /** + * The bounds of the viewport [xmin, ymin, xmax, ymax] for rendering the Voronoi diagram. + * These values only affect the rendering methods (voronoi.render, voronoi.renderBounds, cell.render). + */ + xmin: number; + ymin: number; + xmax: number; + ymax: number; + + /** + * Returns true if the cell with the specified index i contains the specified point ⟨x, y⟩. + * (This method is not affected by the associated Voronoi diagram’s viewport bounds.) + */ + contains(i: number, x: number, y: number): boolean; + + /** + * Returns the convex, closed polygon [[x0, y0], [x1, y1], ..., [x0, y0]] representing the cell for the specified point i. + */ + cellPolygon(i: number): PolygonI; + + /** + * Returns an iterable over the polygons for each cell, in order. + */ + cellPolygons(): IterableIterator; +} + +// https://github.com/d3/d3-delaunay v5.2.1 Copyright 2020 Mike Bostock +// https://github.com/mapbox/delaunator v4.0.1. Copyright 2019 Mapbox, Inc. + +// Type definitions for d3-delaunay 4.1 +// Project: https://github.com/d3/d3-delaunay +// Definitions by: Bradley Odell +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +const EPSILON = Math.pow(2, -52); +const EDGE_STACK = new Uint32Array(512); + +class Delaunator { + static from(points, getX = defaultGetX, getY = defaultGetY) { + const n = points.length; + const coords = new Float64Array(n * 2); + + for (let i = 0; i < n; i++) { + const p = points[i]; + coords[2 * i] = getX(p); + coords[2 * i + 1] = getY(p); + } + + return new Delaunator(coords); + } + + constructor(coords) { + const n = coords.length >> 1; + if (n > 0 && typeof coords[0] !== 'number') throw new Error('Expected coords to contain numbers.'); + + this.coords = coords; + + // arrays that will store the triangulation graph + const maxTriangles = Math.max(2 * n - 5, 0); + this._triangles = new Uint32Array(maxTriangles * 3); + this._halfedges = new Int32Array(maxTriangles * 3); + + // temporary arrays for tracking the edges of the advancing convex hull + this._hashSize = Math.ceil(Math.sqrt(n)); + this._hullPrev = new Uint32Array(n); // edge to prev edge + this._hullNext = new Uint32Array(n); // edge to next edge + this._hullTri = new Uint32Array(n); // edge to adjacent triangle + this._hullHash = new Int32Array(this._hashSize).fill(-1); // angular edge hash + + // temporary arrays for sorting points + this._ids = new Uint32Array(n); + this._dists = new Float64Array(n); + + this.update(); + } + + update() { + const { coords, _hullPrev: hullPrev, _hullNext: hullNext, _hullTri: hullTri, _hullHash: hullHash } = this; + const n = coords.length >> 1; + + // populate an array of point indices; calculate input data bbox + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + + for (let i = 0; i < n; i++) { + const x = coords[2 * i]; + const y = coords[2 * i + 1]; + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + this._ids[i] = i; + } + const cx = (minX + maxX) / 2; + const cy = (minY + maxY) / 2; + + let minDist = Infinity; + let i0, i1, i2; + + // pick a seed point close to the center + for (let i = 0; i < n; i++) { + const d = dist(cx, cy, coords[2 * i], coords[2 * i + 1]); + if (d < minDist) { + i0 = i; + minDist = d; + } + } + const i0x = coords[2 * i0]; + const i0y = coords[2 * i0 + 1]; + + minDist = Infinity; + + // find the point closest to the seed + for (let i = 0; i < n; i++) { + if (i === i0) continue; + const d = dist(i0x, i0y, coords[2 * i], coords[2 * i + 1]); + if (d < minDist && d > 0) { + i1 = i; + minDist = d; + } + } + let i1x = coords[2 * i1]; + let i1y = coords[2 * i1 + 1]; + + let minRadius = Infinity; + + // find the third point which forms the smallest circumcircle with the first two + for (let i = 0; i < n; i++) { + if (i === i0 || i === i1) continue; + const r = circumradius(i0x, i0y, i1x, i1y, coords[2 * i], coords[2 * i + 1]); + if (r < minRadius) { + i2 = i; + minRadius = r; + } + } + let i2x = coords[2 * i2]; + let i2y = coords[2 * i2 + 1]; + + if (minRadius === Infinity) { + // order collinear points by dx (or dy if all x are identical) + // and return the list as a hull + for (let i = 0; i < n; i++) { + this._dists[i] = coords[2 * i] - coords[0] || coords[2 * i + 1] - coords[1]; + } + quicksort(this._ids, this._dists, 0, n - 1); + const hull = new Uint32Array(n); + let j = 0; + for (let i = 0, d0 = -Infinity; i < n; i++) { + const id = this._ids[i]; + if (this._dists[id] > d0) { + hull[j++] = id; + d0 = this._dists[id]; + } + } + this.hull = hull.subarray(0, j); + this.triangles = new Uint32Array(0); + this.halfedges = new Uint32Array(0); + return; + } + + // swap the order of the seed points for counter-clockwise orientation + if (orient(i0x, i0y, i1x, i1y, i2x, i2y)) { + const i = i1; + const x = i1x; + const y = i1y; + i1 = i2; + i1x = i2x; + i1y = i2y; + i2 = i; + i2x = x; + i2y = y; + } + + const center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y); + this._cx = center.x; + this._cy = center.y; + + for (let i = 0; i < n; i++) { + this._dists[i] = dist(coords[2 * i], coords[2 * i + 1], center.x, center.y); + } + + // sort the points by distance from the seed triangle circumcenter + quicksort(this._ids, this._dists, 0, n - 1); + + // set up the seed triangle as the starting hull + this._hullStart = i0; + let hullSize = 3; + + hullNext[i0] = hullPrev[i2] = i1; + hullNext[i1] = hullPrev[i0] = i2; + hullNext[i2] = hullPrev[i1] = i0; + + hullTri[i0] = 0; + hullTri[i1] = 1; + hullTri[i2] = 2; + + hullHash.fill(-1); + hullHash[this._hashKey(i0x, i0y)] = i0; + hullHash[this._hashKey(i1x, i1y)] = i1; + hullHash[this._hashKey(i2x, i2y)] = i2; + + this.trianglesLen = 0; + this._addTriangle(i0, i1, i2, -1, -1, -1); + + for (let k = 0, xp, yp; k < this._ids.length; k++) { + const i = this._ids[k]; + const x = coords[2 * i]; + const y = coords[2 * i + 1]; + + // skip near-duplicate points + if (k > 0 && Math.abs(x - xp) <= EPSILON && Math.abs(y - yp) <= EPSILON) continue; + xp = x; + yp = y; + + // skip seed triangle points + if (i === i0 || i === i1 || i === i2) continue; + + // find a visible edge on the convex hull using edge hash + let start = 0; + for (let j = 0, key = this._hashKey(x, y); j < this._hashSize; j++) { + start = hullHash[(key + j) % this._hashSize]; + if (start !== -1 && start !== hullNext[start]) break; + } + + start = hullPrev[start]; + let e = start, + q; + while (((q = hullNext[e]), !orient(x, y, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1]))) { + e = q; + if (e === start) { + e = -1; + break; + } + } + if (e === -1) continue; // likely a near-duplicate point; skip it + + // add the first triangle from the point + let t = this._addTriangle(e, i, hullNext[e], -1, -1, hullTri[e]); + + // recursively flip triangles from the point until they satisfy the Delaunay condition + hullTri[i] = this._legalize(t + 2); + hullTri[e] = t; // keep track of boundary triangles on the hull + hullSize++; + + // walk forward through the hull, adding more triangles and flipping recursively + let n = hullNext[e]; + while (((q = hullNext[n]), orient(x, y, coords[2 * n], coords[2 * n + 1], coords[2 * q], coords[2 * q + 1]))) { + t = this._addTriangle(n, i, q, hullTri[i], -1, hullTri[n]); + hullTri[i] = this._legalize(t + 2); + hullNext[n] = n; // mark as removed + hullSize--; + n = q; + } + + // walk backward from the other side, adding more triangles and flipping + if (e === start) { + while (((q = hullPrev[e]), orient(x, y, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1]))) { + t = this._addTriangle(q, i, e, -1, hullTri[e], hullTri[q]); + this._legalize(t + 2); + hullTri[q] = t; + hullNext[e] = e; // mark as removed + hullSize--; + e = q; + } + } + + // update the hull indices + this._hullStart = hullPrev[i] = e; + hullNext[e] = hullPrev[n] = i; + hullNext[i] = n; + + // save the two new edges in the hash table + hullHash[this._hashKey(x, y)] = i; + hullHash[this._hashKey(coords[2 * e], coords[2 * e + 1])] = e; + } + + this.hull = new Uint32Array(hullSize); + for (let i = 0, e = this._hullStart; i < hullSize; i++) { + this.hull[i] = e; + e = hullNext[e]; + } + + // trim typed triangle mesh arrays + this.triangles = this._triangles.subarray(0, this.trianglesLen); + this.halfedges = this._halfedges.subarray(0, this.trianglesLen); + } + + _hashKey(x, y) { + return Math.floor(pseudoAngle(x - this._cx, y - this._cy) * this._hashSize) % this._hashSize; + } + + _legalize(a) { + const { _triangles: triangles, _halfedges: halfedges, coords } = this; + + let i = 0; + let ar = 0; + + // recursion eliminated with a fixed-size stack + while (true) { + const b = halfedges[a]; + + /* if the pair of triangles doesn't satisfy the Delaunay condition + * (p1 is inside the circumcircle of [p0, pl, pr]), flip them, + * then do the same check/flip recursively for the new pair of triangles + * + * pl pl + * /||\ / \ + * al/ || \bl al/ \a + * / || \ / \ + * / a||b \ flip /___ar___\ + * p0\ || /p1 => p0\---bl---/p1 + * \ || / \ / + * ar\ || /br b\ /br + * \||/ \ / + * pr pr + */ + const a0 = a - (a % 3); + ar = a0 + ((a + 2) % 3); + + if (b === -1) { + // convex hull edge + if (i === 0) break; + a = EDGE_STACK[--i]; + continue; + } + + const b0 = b - (b % 3); + const al = a0 + ((a + 1) % 3); + const bl = b0 + ((b + 2) % 3); + + const p0 = triangles[ar]; + const pr = triangles[a]; + const pl = triangles[al]; + const p1 = triangles[bl]; + + const illegal = inCircle( + coords[2 * p0], + coords[2 * p0 + 1], + coords[2 * pr], + coords[2 * pr + 1], + coords[2 * pl], + coords[2 * pl + 1], + coords[2 * p1], + coords[2 * p1 + 1], + ); + + if (illegal) { + triangles[a] = p1; + triangles[b] = p0; + + const hbl = halfedges[bl]; + + // edge swapped on the other side of the hull (rare); fix the halfedge reference + if (hbl === -1) { + let e = this._hullStart; + do { + if (this._hullTri[e] === bl) { + this._hullTri[e] = a; + break; + } + e = this._hullPrev[e]; + } while (e !== this._hullStart); + } + this._link(a, hbl); + this._link(b, halfedges[ar]); + this._link(ar, bl); + + const br = b0 + ((b + 1) % 3); + + // don't worry about hitting the cap: it can only happen on extremely degenerate input + if (i < EDGE_STACK.length) { + EDGE_STACK[i++] = br; + } + } else { + if (i === 0) break; + a = EDGE_STACK[--i]; + } + } + + return ar; + } + + _link(a, b) { + this._halfedges[a] = b; + if (b !== -1) this._halfedges[b] = a; + } + + // add a new triangle given vertex indices and adjacent half-edge ids + _addTriangle(i0, i1, i2, a, b, c) { + const t = this.trianglesLen; + + this._triangles[t] = i0; + this._triangles[t + 1] = i1; + this._triangles[t + 2] = i2; + + this._link(t, a); + this._link(t + 1, b); + this._link(t + 2, c); + + this.trianglesLen += 3; + + return t; + } +} + +// monotonically increases with real angle, but doesn't need expensive trigonometry +function pseudoAngle(dx, dy) { + const p = dx / (Math.abs(dx) + Math.abs(dy)); + return (dy > 0 ? 3 - p : 1 + p) / 4; // [0..1] +} + +function dist(ax, ay, bx, by) { + const dx = ax - bx; + const dy = ay - by; + return dx * dx + dy * dy; +} + +// return 2d orientation sign if we're confident in it through J. Shewchuk's error bound check +function orientIfSure(px, py, rx, ry, qx, qy) { + const l = (ry - py) * (qx - px); + const r = (rx - px) * (qy - py); + return Math.abs(l - r) >= 3.3306690738754716e-16 * Math.abs(l + r) ? l - r : 0; +} + +// a more robust orientation test that's stable in a given triangle (to fix robustness issues) +function orient(rx, ry, qx, qy, px, py) { + const sign = + orientIfSure(px, py, rx, ry, qx, qy) || + orientIfSure(rx, ry, qx, qy, px, py) || + orientIfSure(qx, qy, px, py, rx, ry); + return sign < 0; +} + +function inCircle(ax, ay, bx, by, cx, cy, px, py) { + const dx = ax - px; + const dy = ay - py; + const ex = bx - px; + const ey = by - py; + const fx = cx - px; + const fy = cy - py; + + const ap = dx * dx + dy * dy; + const bp = ex * ex + ey * ey; + const cp = fx * fx + fy * fy; + + return dx * (ey * cp - bp * fy) - dy * (ex * cp - bp * fx) + ap * (ex * fy - ey * fx) < 0; +} + +function circumradius(ax, ay, bx, by, cx, cy) { + const dx = bx - ax; + const dy = by - ay; + const ex = cx - ax; + const ey = cy - ay; + + const bl = dx * dx + dy * dy; + const cl = ex * ex + ey * ey; + const d = 0.5 / (dx * ey - dy * ex); + + const x = (ey * bl - dy * cl) * d; + const y = (dx * cl - ex * bl) * d; + + return x * x + y * y; +} + +function circumcenter(ax, ay, bx, by, cx, cy) { + const dx = bx - ax; + const dy = by - ay; + const ex = cx - ax; + const ey = cy - ay; + + const bl = dx * dx + dy * dy; + const cl = ex * ex + ey * ey; + const d = 0.5 / (dx * ey - dy * ex); + + const x = ax + (ey * bl - dy * cl) * d; + const y = ay + (dx * cl - ex * bl) * d; + + return { x, y }; +} + +function quicksort(ids, dists, left, right) { + if (right - left <= 20) { + for (let i = left + 1; i <= right; i++) { + const temp = ids[i]; + const tempDist = dists[temp]; + let j = i - 1; + while (j >= left && dists[ids[j]] > tempDist) ids[j + 1] = ids[j--]; + ids[j + 1] = temp; + } + } else { + const median = (left + right) >> 1; + let i = left + 1; + let j = right; + swap(ids, median, i); + if (dists[ids[left]] > dists[ids[right]]) swap(ids, left, right); + if (dists[ids[i]] > dists[ids[right]]) swap(ids, i, right); + if (dists[ids[left]] > dists[ids[i]]) swap(ids, left, i); + + const temp = ids[i]; + const tempDist = dists[temp]; + while (true) { + do i++; + while (dists[ids[i]] < tempDist); + do j--; + while (dists[ids[j]] > tempDist); + if (j < i) break; + swap(ids, i, j); + } + ids[left + 1] = ids[j]; + ids[j] = temp; + + if (right - i + 1 >= j - left) { + quicksort(ids, dists, i, right); + quicksort(ids, dists, left, j - 1); + } else { + quicksort(ids, dists, left, j - 1); + quicksort(ids, dists, i, right); + } + } +} + +function swap(arr, i, j) { + const tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; +} + +function defaultGetX(p) { + return p[0]; +} +function defaultGetY(p) { + return p[1]; +} + +const epsilon = 1e-6; + +class Path { + constructor() { + this._x0 = this._y0 = this._x1 = this._y1 = null; // start of current subpath // end of current subpath + this._ = ''; + } + moveTo(x, y) { + this._ += `M${(this._x0 = this._x1 = +x)},${(this._y0 = this._y1 = +y)}`; + } + closePath() { + if (this._x1 !== null) { + (this._x1 = this._x0), (this._y1 = this._y0); + this._ += 'Z'; + } + } + lineTo(x, y) { + this._ += `L${(this._x1 = +x)},${(this._y1 = +y)}`; + } + arc(x, y, r) { + (x = +x), (y = +y), (r = +r); + const x0 = x + r; + const y0 = y; + if (r < 0) throw new Error('negative radius'); + if (this._x1 === null) this._ += `M${x0},${y0}`; + else if (Math.abs(this._x1 - x0) > epsilon || Math.abs(this._y1 - y0) > epsilon) this._ += 'L' + x0 + ',' + y0; + if (!r) return; + this._ += `A${r},${r},0,1,1,${x - r},${y}A${r},${r},0,1,1,${(this._x1 = x0)},${(this._y1 = y0)}`; + } + rect(x, y, w, h) { + this._ += `M${(this._x0 = this._x1 = +x)},${(this._y0 = this._y1 = +y)}h${+w}v${+h}h${-w}Z`; + } + value() { + return this._ || null; + } +} + +class Polygon { + constructor() { + this._ = []; + } + moveTo(x, y) { + this._.push([x, y]); + } + closePath() { + this._.push(this._[0].slice()); + } + lineTo(x, y) { + this._.push([x, y]); + } + value() { + return this._.length ? this._ : null; + } +} + +export class Voronoi

implements VoronoiI

{ + xmin: number; + ymin: number; + xmax: number; + ymax: number; + /** + * The Voronoi diagram’s associated Delaunay triangulation. + */ + delaunay: DelaunayI

; + + /** + * The circumcenters of the Delaunay triangles [cx0, cy0, cx1, cy1, ...]. + * Each contiguous pair of coordinates cx, cy is the circumcenter for the corresponding triangle. + * These circumcenters form the coordinates of the Voronoi cell polygons. + */ + circumcenters: Float64Array; + + /** + * An array [vx0, vy0, wx0, wy0, ...] where each non-zero quadruple describes an open (infinite) cell + * on the outer hull, giving the directions of two open half-lines. + */ + vectors: Float64Array; + + constructor(delaunay: DelaunayI

, [xmin, ymin, xmax, ymax]: Bounds = [0, 0, 960, 500]) { + if (!((xmax = +xmax) >= (xmin = +xmin)) || !((ymax = +ymax) >= (ymin = +ymin))) throw new Error('invalid bounds'); + this.delaunay = delaunay; + this._circumcenters = new Float64Array(delaunay.points.length * 2); + this.vectors = new Float64Array(delaunay.points.length * 2); + (this.xmax = xmax), (this.xmin = xmin); + (this.ymax = ymax), (this.ymin = ymin); + this._init(); + } + update() { + this.delaunay.update(); + this._init(); + return this; + } + _init() { + const { + delaunay: { points, hull, triangles }, + vectors, + } = this; + + // Compute circumcenters. + const circumcenters = (this.circumcenters = this._circumcenters.subarray(0, (triangles.length / 3) * 2)); + for (let i = 0, j = 0, n = triangles.length, x, y; i < n; i += 3, j += 2) { + const t1 = triangles[i] * 2; + const t2 = triangles[i + 1] * 2; + const t3 = triangles[i + 2] * 2; + const x1 = points[t1]; + const y1 = points[t1 + 1]; + const x2 = points[t2]; + const y2 = points[t2 + 1]; + const x3 = points[t3]; + const y3 = points[t3 + 1]; + + const dx = x2 - x1; + const dy = y2 - y1; + const ex = x3 - x1; + const ey = y3 - y1; + const bl = dx * dx + dy * dy; + const cl = ex * ex + ey * ey; + const ab = (dx * ey - dy * ex) * 2; + + if (!ab) { + // degenerate case (collinear diagram) + x = (x1 + x3) / 2 - 1e8 * ey; + y = (y1 + y3) / 2 + 1e8 * ex; + } else if (Math.abs(ab) < 1e-8) { + // almost equal points (degenerate triangle) + x = (x1 + x3) / 2; + y = (y1 + y3) / 2; + } else { + const d = 1 / ab; + x = x1 + (ey * bl - dy * cl) * d; + y = y1 + (dx * cl - ex * bl) * d; + } + circumcenters[j] = x; + circumcenters[j + 1] = y; + } + + // Compute exterior cell rays. + let h = hull[hull.length - 1]; + let p0, + p1 = h * 4; + let x0, + x1 = points[2 * h]; + let y0, + y1 = points[2 * h + 1]; + vectors.fill(0); + for (let i = 0; i < hull.length; ++i) { + h = hull[i]; + (p0 = p1), (x0 = x1), (y0 = y1); + (p1 = h * 4), (x1 = points[2 * h]), (y1 = points[2 * h + 1]); + vectors[p0 + 2] = vectors[p1] = y0 - y1; + vectors[p0 + 3] = vectors[p1 + 1] = x1 - x0; + } + } + /** + * Renders the mesh of Voronoi cells to the specified context. + * The specified context must implement the context.moveTo and context.lineTo methods from the CanvasPathMethods API. + */ + render(context: MoveContext & LineContext): void { + const buffer = context == null ? (context = new Path()) : undefined; + const { + delaunay: { halfedges, inedges, hull }, + circumcenters, + vectors, + } = this; + if (hull.length <= 1) return null; + for (let i = 0, n = halfedges.length; i < n; ++i) { + const j = halfedges[i]; + if (j < i) continue; + const ti = Math.floor(i / 3) * 2; + const tj = Math.floor(j / 3) * 2; + const xi = circumcenters[ti]; + const yi = circumcenters[ti + 1]; + const xj = circumcenters[tj]; + const yj = circumcenters[tj + 1]; + this._renderSegment(xi, yi, xj, yj, context); + } + let h0, + h1 = hull[hull.length - 1]; + for (let i = 0; i < hull.length; ++i) { + (h0 = h1), (h1 = hull[i]); + const t = Math.floor(inedges[h1] / 3) * 2; + const x = circumcenters[t]; + const y = circumcenters[t + 1]; + const v = h0 * 4; + const p = this._project(x, y, vectors[v + 2], vectors[v + 3]); + if (p) this._renderSegment(x, y, p[0], p[1], context); + } + return buffer && buffer.value(); + } + /** + * Renders the viewport extent to the specified context. + * The specified context must implement the context.rect method from the CanvasPathMethods API. + * Equivalent to context.rect(voronoi.xmin, voronoi.ymin, voronoi.xmax - voronoi.xmin, voronoi.ymax - voronoi.ymin). + */ + renderBounds(context: RectContext): void { + const buffer = context == null ? (context = new Path()) : undefined; + context.rect(this.xmin, this.ymin, this.xmax - this.xmin, this.ymax - this.ymin); + return buffer && buffer.value(); + } + + /** + * Renders the cell with the specified index i to the specified context. + * The specified context must implement the context.moveTo, context.lineTo, and context.closePath methods from the CanvasPathMethods API. + */ + renderCell(i: number, context: MoveContext & LineContext & ClosableContext): void { + const buffer = context == null ? (context = new Path()) : undefined; + const points = this._clip(i); + if (points === null) return; + context.moveTo(points[0], points[1]); + let n = points.length; + while (points[0] === points[n - 2] && points[1] === points[n - 1] && n > 1) n -= 2; + for (let i = 2; i < n; i += 2) { + if (points[i] !== points[i - 2] || points[i + 1] !== points[i - 1]) context.lineTo(points[i], points[i + 1]); + } + context.closePath(); + return buffer && buffer.value(); + } + *cellPolygons() { + const { + delaunay: { points }, + } = this; + for (let i = 0, n = points.length / 2; i < n; ++i) { + const cell = this.cellPolygon(i); + if (cell) yield cell; + } + } + cellPolygon(i) { + const polygon = new Polygon(); + this.renderCell(i, polygon); + return polygon.value(); + } + _renderSegment(x0, y0, x1, y1, context) { + let S; + const c0 = this._regioncode(x0, y0); + const c1 = this._regioncode(x1, y1); + if (c0 === 0 && c1 === 0) { + context.moveTo(x0, y0); + context.lineTo(x1, y1); + } else if ((S = this._clipSegment(x0, y0, x1, y1, c0, c1))) { + context.moveTo(S[0], S[1]); + context.lineTo(S[2], S[3]); + } + } + contains(i, x, y) { + if (((x = +x), x !== x) || ((y = +y), y !== y)) return false; + return this.delaunay._step(i, x, y) === i; + } + *neighbors(i) { + const ci = this._clip(i); + if (ci) + for (const j of this.delaunay.neighbors(i)) { + const cj = this._clip(j); + // find the common edge + if (cj) + loop: for (let ai = 0, li = ci.length; ai < li; ai += 2) { + for (let aj = 0, lj = cj.length; aj < lj; aj += 2) { + if ( + ci[ai] == cj[aj] && + ci[ai + 1] == cj[aj + 1] && + ci[(ai + 2) % li] == cj[(aj + lj - 2) % lj] && + ci[(ai + 3) % li] == cj[(aj + lj - 1) % lj] + ) { + yield j; + break loop; + } + } + } + } + } + _cell(i) { + const { + circumcenters, + delaunay: { inedges, halfedges, triangles }, + } = this; + const e0 = inedges[i]; + if (e0 === -1) return null; // coincident point + const points = []; + let e = e0; + do { + const t = Math.floor(e / 3); + points.push(circumcenters[t * 2], circumcenters[t * 2 + 1]); + e = e % 3 === 2 ? e - 2 : e + 1; + if (triangles[e] !== i) break; // bad triangulation + e = halfedges[e]; + } while (e !== e0 && e !== -1); + return points; + } + _clip(i) { + // degenerate case (1 valid point: return the box) + if (i === 0 && this.delaunay.hull.length === 1) { + return [this.xmax, this.ymin, this.xmax, this.ymax, this.xmin, this.ymax, this.xmin, this.ymin]; + } + const points = this._cell(i); + if (points === null) return null; + const { vectors: V } = this; + const v = i * 4; + return V[v] || V[v + 1] + ? this._clipInfinite(i, points, V[v], V[v + 1], V[v + 2], V[v + 3]) + : this._clipFinite(i, points); + } + _clipFinite(i, points) { + const n = points.length; + let P = null; + let x0, + y0, + x1 = points[n - 2], + y1 = points[n - 1]; + let c0, + c1 = this._regioncode(x1, y1); + let e0, e1; + for (let j = 0; j < n; j += 2) { + (x0 = x1), (y0 = y1), (x1 = points[j]), (y1 = points[j + 1]); + (c0 = c1), (c1 = this._regioncode(x1, y1)); + if (c0 === 0 && c1 === 0) { + (e0 = e1), (e1 = 0); + if (P) P.push(x1, y1); + else P = [x1, y1]; + } else { + let S, sx0, sy0, sx1, sy1; + if (c0 === 0) { + if ((S = this._clipSegment(x0, y0, x1, y1, c0, c1)) === null) continue; + [sx0, sy0, sx1, sy1] = S; + } else { + if ((S = this._clipSegment(x1, y1, x0, y0, c1, c0)) === null) continue; + [sx1, sy1, sx0, sy0] = S; + (e0 = e1), (e1 = this._edgecode(sx0, sy0)); + if (e0 && e1) this._edge(i, e0, e1, P, P.length); + if (P) P.push(sx0, sy0); + else P = [sx0, sy0]; + } + (e0 = e1), (e1 = this._edgecode(sx1, sy1)); + if (e0 && e1) this._edge(i, e0, e1, P, P.length); + if (P) P.push(sx1, sy1); + else P = [sx1, sy1]; + } + } + if (P) { + (e0 = e1), (e1 = this._edgecode(P[0], P[1])); + if (e0 && e1) this._edge(i, e0, e1, P, P.length); + } else if (this.contains(i, (this.xmin + this.xmax) / 2, (this.ymin + this.ymax) / 2)) { + return [this.xmax, this.ymin, this.xmax, this.ymax, this.xmin, this.ymax, this.xmin, this.ymin]; + } + return P; + } + _clipSegment(x0, y0, x1, y1, c0, c1) { + while (true) { + if (c0 === 0 && c1 === 0) return [x0, y0, x1, y1]; + if (c0 & c1) return null; + let x, + y, + c = c0 || c1; + if (c & 0b1000) (x = x0 + ((x1 - x0) * (this.ymax - y0)) / (y1 - y0)), (y = this.ymax); + else if (c & 0b0100) (x = x0 + ((x1 - x0) * (this.ymin - y0)) / (y1 - y0)), (y = this.ymin); + else if (c & 0b0010) (y = y0 + ((y1 - y0) * (this.xmax - x0)) / (x1 - x0)), (x = this.xmax); + else (y = y0 + ((y1 - y0) * (this.xmin - x0)) / (x1 - x0)), (x = this.xmin); + if (c0) (x0 = x), (y0 = y), (c0 = this._regioncode(x0, y0)); + else (x1 = x), (y1 = y), (c1 = this._regioncode(x1, y1)); + } + } + _clipInfinite(i, points, vx0, vy0, vxn, vyn) { + let P = Array.from(points), + p; + if ((p = this._project(P[0], P[1], vx0, vy0))) P.unshift(p[0], p[1]); + if ((p = this._project(P[P.length - 2], P[P.length - 1], vxn, vyn))) P.push(p[0], p[1]); + if ((P = this._clipFinite(i, P))) { + for (let j = 0, n = P.length, c0, c1 = this._edgecode(P[n - 2], P[n - 1]); j < n; j += 2) { + (c0 = c1), (c1 = this._edgecode(P[j], P[j + 1])); + if (c0 && c1) (j = this._edge(i, c0, c1, P, j)), (n = P.length); + } + } else if (this.contains(i, (this.xmin + this.xmax) / 2, (this.ymin + this.ymax) / 2)) { + P = [this.xmin, this.ymin, this.xmax, this.ymin, this.xmax, this.ymax, this.xmin, this.ymax]; + } + return P; + } + _edge(i, e0, e1, P, j) { + while (e0 !== e1) { + let x, y; + switch (e0) { + case 0b0101: + e0 = 0b0100; + continue; // top-left + case 0b0100: + (e0 = 0b0110), (x = this.xmax), (y = this.ymin); + break; // top + case 0b0110: + e0 = 0b0010; + continue; // top-right + case 0b0010: + (e0 = 0b1010), (x = this.xmax), (y = this.ymax); + break; // right + case 0b1010: + e0 = 0b1000; + continue; // bottom-right + case 0b1000: + (e0 = 0b1001), (x = this.xmin), (y = this.ymax); + break; // bottom + case 0b1001: + e0 = 0b0001; + continue; // bottom-left + case 0b0001: + (e0 = 0b0101), (x = this.xmin), (y = this.ymin); + break; // left + } + if ((P[j] !== x || P[j + 1] !== y) && this.contains(i, x, y)) { + P.splice(j, 0, x, y), (j += 2); + } + } + if (P.length > 4) { + for (let i = 0; i < P.length; i += 2) { + const j = (i + 2) % P.length, + k = (i + 4) % P.length; + if ((P[i] === P[j] && P[j] === P[k]) || (P[i + 1] === P[j + 1] && P[j + 1] === P[k + 1])) + P.splice(j, 2), (i -= 2); + } + } + return j; + } + _project(x0, y0, vx, vy) { + let t = Infinity, + c, + x, + y; + if (vy < 0) { + // top + if (y0 <= this.ymin) return null; + if ((c = (this.ymin - y0) / vy) < t) (y = this.ymin), (x = x0 + (t = c) * vx); + } else if (vy > 0) { + // bottom + if (y0 >= this.ymax) return null; + if ((c = (this.ymax - y0) / vy) < t) (y = this.ymax), (x = x0 + (t = c) * vx); + } + if (vx > 0) { + // right + if (x0 >= this.xmax) return null; + if ((c = (this.xmax - x0) / vx) < t) (x = this.xmax), (y = y0 + (t = c) * vy); + } else if (vx < 0) { + // left + if (x0 <= this.xmin) return null; + if ((c = (this.xmin - x0) / vx) < t) (x = this.xmin), (y = y0 + (t = c) * vy); + } + return [x, y]; + } + _edgecode(x, y) { + return ( + (x === this.xmin ? 0b0001 : x === this.xmax ? 0b0010 : 0b0000) | + (y === this.ymin ? 0b0100 : y === this.ymax ? 0b1000 : 0b0000) + ); + } + _regioncode(x, y) { + return ( + (x < this.xmin ? 0b0001 : x > this.xmax ? 0b0010 : 0b0000) | + (y < this.ymin ? 0b0100 : y > this.ymax ? 0b1000 : 0b0000) + ); + } +} + +const tau = 2 * Math.PI; + +function pointX(p) { + return p[0]; +} + +function pointY(p) { + return p[1]; +} + +// A triangulation is collinear if all its triangles have a non-null area +function collinear(d) { + const { triangles, coords } = d; + for (let i = 0; i < triangles.length; i += 3) { + const a = 2 * triangles[i], + b = 2 * triangles[i + 1], + c = 2 * triangles[i + 2], + cross = + (coords[c] - coords[a]) * (coords[b + 1] - coords[a + 1]) - + (coords[b] - coords[a]) * (coords[c + 1] - coords[a + 1]); + if (cross > 1e-10) return false; + } + return true; +} + +function jitter(x, y, r) { + return [x + Math.sin(x + y) * r, y + Math.cos(x - y) * r]; +} + +export class Delaunay

implements DelaunayI

{ + /** + * The coordinates of the points as an array [x0, y0, x1, y1, ...]. + * Typically, this is a Float64Array, however you can use any array-like type in the constructor. + */ + points: ArrayLike; + + /** + * The halfedge indices as an Int32Array [j0, j1, ...]. + * For each index 0 <= i < halfedges.length, there is a halfedge from triangle vertex j = halfedges[i] to triangle vertex i. + */ + halfedges: Int32Array; + + /** + * An arbitrary node on the convex hull. + * The convex hull is represented as a circular doubly-linked list of nodes. + */ + hull: Node; + + /** + * The triangle vertex indices as an Uint32Array [i0, j0, k0, i1, j1, k1, ...]. + * Each contiguous triplet of indices i, j, k forms a counterclockwise triangle. + * The coordinates of the triangle's points can be found by going through 'points'. + */ + triangles: Uint32Array; + + /** + * The incoming halfedge indexes as a Int32Array [e0, e1, e2, ...]. + * For each point i, inedges[i] is the halfedge index e of an incoming halfedge. + * For coincident points, the halfedge index is -1; for points on the convex hull, the incoming halfedge is on the convex hull; for other points, the choice of incoming halfedge is arbitrary. + */ + inedges: Int32Array; + + /** + * The outgoing halfedge indexes as a Int32Array [e0, e1, e2, ...]. + * For each point i on the convex hull, outedges[i] is the halfedge index e of the corresponding outgoing halfedge; for other points, the halfedge index is -1. + */ + outedges: Int32Array; + /** + * Returns the Delaunay triangulation for the given array or iterable of points. + * Otherwise, the getX and getY functions are invoked for each point in order, and must return the respective x- and y-coordinate for each point. + * If that is specified, the functions getX and getY are invoked with that as this. + * (See Array.from for reference.) + */ + static from

( + points: ArrayLike

| Iterable

, + fx: GetCoordinate | Iterable

> = pointX, + fy: GetCoordinate | Iterable

> = pointY, + that?: any, + ): Delaunay

{ + return new Delaunay( + 'length' in points ? flatArray(points, fx, fy, that) : Float64Array.from(flatIterable(points, fx, fy, that)), + ); + } + /** + * Returns the Delaunay triangulation for the given flat array [x0, y0, x1, y1, …] of points. + */ + constructor(points: ArrayLike) { + this._delaunator = new Delaunator(points); + this.inedges = new Int32Array(points.length / 2); + this._hullIndex = new Int32Array(points.length / 2); + this.points = this._delaunator.coords; + this._init(); + } + update() { + this._delaunator.update(); + this._init(); + return this; + } + _init() { + const d = this._delaunator, + points = this.points; + + // check for collinear + if (d.hull && d.hull.length > 2 && collinear(d)) { + this.collinear = Int32Array.from({ length: points.length / 2 }, (_, i) => i).sort( + (i, j) => points[2 * i] - points[2 * j] || points[2 * i + 1] - points[2 * j + 1], + ); // for exact neighbors + const e = this.collinear[0], + f = this.collinear[this.collinear.length - 1], + bounds = [points[2 * e], points[2 * e + 1], points[2 * f], points[2 * f + 1]], + r = 1e-8 * Math.sqrt((bounds[3] - bounds[1]) ** 2 + (bounds[2] - bounds[0]) ** 2); + for (let i = 0, n = points.length / 2; i < n; ++i) { + const p = jitter(points[2 * i], points[2 * i + 1], r); + points[2 * i] = p[0]; + points[2 * i + 1] = p[1]; + } + this._delaunator = new Delaunator(points); + } else { + delete this.collinear; + } + + const halfedges = (this.halfedges = this._delaunator.halfedges); + const hull = (this.hull = this._delaunator.hull); + const triangles = (this.triangles = this._delaunator.triangles); + const inedges = this.inedges.fill(-1); + const hullIndex = this._hullIndex.fill(-1); + + // Compute an index from each point to an (arbitrary) incoming halfedge + // Used to give the first neighbor of each point; for this reason, + // on the hull we give priority to exterior halfedges + for (let e = 0, n = halfedges.length; e < n; ++e) { + const p = triangles[e % 3 === 2 ? e - 2 : e + 1]; + if (halfedges[e] === -1 || inedges[p] === -1) inedges[p] = e; + } + for (let i = 0, n = hull.length; i < n; ++i) { + hullIndex[hull[i]] = i; + } + + // degenerate case: 1 or 2 (distinct) points + if (hull.length <= 2 && hull.length > 0) { + this.triangles = new Int32Array(3).fill(-1); + this.halfedges = new Int32Array(3).fill(-1); + this.triangles[0] = hull[0]; + this.triangles[1] = hull[1]; + this.triangles[2] = hull[1]; + inedges[hull[0]] = 1; + if (hull.length === 2) inedges[hull[1]] = 0; + } + } + voronoi(bounds) { + return new Voronoi(this, bounds); + } + *neighbors(i) { + const { inedges, hull, _hullIndex, halfedges, triangles, collinear } = this; + + // degenerate case with several collinear points + if (collinear) { + const l = collinear.indexOf(i); + if (l > 0) yield collinear[l - 1]; + if (l < collinear.length - 1) yield collinear[l + 1]; + return; + } + + const e0 = inedges[i]; + if (e0 === -1) return; // coincident point + let e = e0, + p0 = -1; + do { + yield (p0 = triangles[e]); + e = e % 3 === 2 ? e - 2 : e + 1; + if (triangles[e] !== i) return; // bad triangulation + e = halfedges[e]; + if (e === -1) { + const p = hull[(_hullIndex[i] + 1) % hull.length]; + if (p !== p0) yield p; + return; + } + } while (e !== e0); + } + find(x, y, i = 0) { + if (((x = +x), x !== x) || ((y = +y), y !== y)) return -1; + const i0 = i; + let c; + while ((c = this._step(i, x, y)) >= 0 && c !== i && c !== i0) i = c; + return c; + } + _step(i, x, y) { + const { inedges, hull, _hullIndex, halfedges, triangles, points } = this; + if (inedges[i] === -1 || !points.length) return (i + 1) % (points.length >> 1); + let c = i; + let dc = (x - points[i * 2]) ** 2 + (y - points[i * 2 + 1]) ** 2; + const e0 = inedges[i]; + let e = e0; + do { + let t = triangles[e]; + const dt = (x - points[t * 2]) ** 2 + (y - points[t * 2 + 1]) ** 2; + if (dt < dc) (dc = dt), (c = t); + e = e % 3 === 2 ? e - 2 : e + 1; + if (triangles[e] !== i) break; // bad triangulation + e = halfedges[e]; + if (e === -1) { + e = hull[(_hullIndex[i] + 1) % hull.length]; + if (e !== t) { + if ((x - points[e * 2]) ** 2 + (y - points[e * 2 + 1]) ** 2 < dc) return e; + } + break; + } + } while (e !== e0); + return c; + } + /** + * Renders the edges of the Delaunay triangulation to the specified context. + * The specified context must implement the context.moveTo and context.lineTo methods from the CanvasPathMethods API. + */ + render(context: MoveContext & LineContext): void { + const buffer = context == null ? (context = new Path()) : undefined; + const { points, halfedges, triangles } = this; + for (let i = 0, n = halfedges.length; i < n; ++i) { + const j = halfedges[i]; + if (j < i) continue; + const ti = triangles[i] * 2; + const tj = triangles[j] * 2; + context.moveTo(points[ti], points[ti + 1]); + context.lineTo(points[tj], points[tj + 1]); + } + this.renderHull(context); + return buffer && buffer.value(); + } + /** + * Renders the input points of the Delaunay triangulation to the specified context as circles with the specified radius. + * If radius is not specified, it defaults to 2. + * The specified context must implement the context.moveTo and context.arc methods from the CanvasPathMethods API. + */ + renderPoints(context: MoveContext & ArcContext, r?: number): void { + const buffer = context == null ? (context = new Path()) : undefined; + const { points } = this; + for (let i = 0, n = points.length; i < n; i += 2) { + const x = points[i], + y = points[i + 1]; + context.moveTo(x + r, y); + context.arc(x, y, r, 0, tau); + } + return buffer && buffer.value(); + } + /** + * Renders the convex hull of the Delaunay triangulation to the specified context. + * The specified context must implement the context.moveTo and context.lineTo methods from the CanvasPathMethods API. + */ + renderHull(context: MoveContext & LineContext): void { + const buffer = context == null ? (context = new Path()) : undefined; + const { hull, points } = this; + const h = hull[0] * 2, + n = hull.length; + context.moveTo(points[h], points[h + 1]); + for (let i = 1; i < n; ++i) { + const h = 2 * hull[i]; + context.lineTo(points[h], points[h + 1]); + } + context.closePath(); + return buffer && buffer.value(); + } + hullPolygon() { + const polygon = new Polygon(); + this.renderHull(polygon); + return polygon.value(); + } + /** + * Renders triangle i of the Delaunay triangulation to the specified context. + * The specified context must implement the context.moveTo, context.lineTo and context.closePath methods from the CanvasPathMethods API. + */ + renderTriangle(i: number, context: MoveContext & LineContext & ClosableContext): void { + const buffer = context == null ? (context = new Path()) : undefined; + const { points, triangles } = this; + const t0 = triangles[(i *= 3)] * 2; + const t1 = triangles[i + 1] * 2; + const t2 = triangles[i + 2] * 2; + context.moveTo(points[t0], points[t0 + 1]); + context.lineTo(points[t1], points[t1 + 1]); + context.lineTo(points[t2], points[t2 + 1]); + context.closePath(); + return buffer && buffer.value(); + } + *trianglePolygons() { + const { triangles } = this; + for (let i = 0, n = triangles.length / 3; i < n; ++i) { + yield this.trianglePolygon(i); + } + } + trianglePolygon(i) { + const polygon = new Polygon(); + this.renderTriangle(i, polygon); + return polygon.value(); + } +} + +function flatArray(points, fx, fy, that) { + const n = points.length; + const array = new Float64Array(n * 2); + for (let i = 0; i < n; ++i) { + const p = points[i]; + array[i * 2] = fx.call(that, p, i, points); + array[i * 2 + 1] = fy.call(that, p, i, points); + } + return array; +} + +function* flatIterable(points, fx, fy, that) { + let i = 0; + for (const p of points) { + yield fx.call(that, p, i, points); + yield fy.call(that, p, i, points); + ++i; + } +} diff --git a/src/utils/data_generators/data_generator.ts b/src/utils/data_generators/data_generator.ts index 34708ef378..44289b366b 100644 --- a/src/utils/data_generators/data_generator.ts +++ b/src/utils/data_generators/data_generator.ts @@ -16,12 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import { Simple1DNoise, RandomNumberGenerator } from './simple_noise'; +import { Simple1DNoise } from './simple_noise'; +import { RandomNumberGenerator } from '../../mocks/utils'; export class DataGenerator { + private randomNumberGenerator: RandomNumberGenerator; private generator: Simple1DNoise; private frequency: number; - constructor(frequency = 500, randomNumberGenerator?: RandomNumberGenerator) { + constructor(frequency = 500, randomNumberGenerator: RandomNumberGenerator) { + this.randomNumberGenerator = randomNumberGenerator; this.generator = new Simple1DNoise(randomNumberGenerator); this.frequency = frequency; } @@ -52,4 +55,23 @@ export class DataGenerator { }); return groups.reduce((acc, curr) => [...acc, ...curr]); } + generateRandomSeries(totalPoints = 50, groupIndex = 1, groupPrefix = '') { + const group = String.fromCharCode(97 + groupIndex); + const dataPoints = new Array(totalPoints).fill(0).map(() => { + return { + x: this.randomNumberGenerator(0, 100), + y: this.randomNumberGenerator(0, 100), + z: this.randomNumberGenerator(0, 100), + g: `${groupPrefix}${group}`, + }; + }); + return dataPoints; + } + generateRandomGroupedSeries(totalPoints = 50, totalGroups = 2, groupPrefix = '') { + const groups = new Array(totalGroups).fill(0).map((group, i) => { + // eslint-disable-line + return this.generateRandomSeries(totalPoints, i, groupPrefix); + }); + return groups.reduce((acc, curr) => [...acc, ...curr]); + } } diff --git a/src/utils/data_generators/simple_noise.ts b/src/utils/data_generators/simple_noise.ts index 1203ca0b07..a1415a7d06 100644 --- a/src/utils/data_generators/simple_noise.ts +++ b/src/utils/data_generators/simple_noise.ts @@ -1,3 +1,5 @@ +import { RandomNumberGenerator } from '../../mocks/utils'; + /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -16,8 +18,6 @@ * specific language governing permissions and limitations * under the License. */ -export type RandomNumberGenerator = () => number; - export class Simple1DNoise { private maxVertices: number; private maxVerticesMask: number; @@ -25,8 +25,8 @@ export class Simple1DNoise { private scale: number; private getRandomNumber: RandomNumberGenerator; - constructor(randomNumberGenerator?: RandomNumberGenerator, maxVertices = 256, amplitude = 5.1, scale = 0.6) { - this.getRandomNumber = randomNumberGenerator ? randomNumberGenerator : Math.random; + constructor(randomNumberGenerator: RandomNumberGenerator, maxVertices = 256, amplitude = 5.1, scale = 0.6) { + this.getRandomNumber = randomNumberGenerator; this.maxVerticesMask = maxVertices - 1; this.amplitude = amplitude; this.scale = scale; @@ -34,7 +34,8 @@ export class Simple1DNoise { } getValue(x: number) { - const r = new Array(this.maxVertices).fill(0).map(this.getRandomNumber); + const r = new Array(this.maxVertices).fill(0).map(() => this.getRandomNumber(0, 1, 5, true)); + const scaledX = x * this.scale; const xFloor = Math.floor(scaledX); const t = scaledX - xFloor; diff --git a/src/utils/fast_deep_equal.ts b/src/utils/fast_deep_equal.ts index 207311fbe3..482ee72a81 100644 --- a/src/utils/fast_deep_equal.ts +++ b/src/utils/fast_deep_equal.ts @@ -1,7 +1,8 @@ /* eslint-disable file-header/file-header */ -/* @notice - * This product includes code that is adapted from fase-deep-equal@3.1.1, +/** + * @notice + * This product includes code that is adapted from fast-deep-equal@3.1.1, * which is available under a "MIT" license. * * MIT License @@ -24,7 +25,8 @@ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. */ + * SOFTWARE. + */ /** @internal */ export function deepEqual(a: any, b: any): boolean { diff --git a/src/utils/geometry.ts b/src/utils/geometry.ts index c43042fd6d..9f1a8e8a99 100644 --- a/src/utils/geometry.ts +++ b/src/utils/geometry.ts @@ -34,6 +34,7 @@ export type BandedAccessorType = $Values; export interface GeometryValue { y: any; x: any; + mark: number | null; accessor: BandedAccessorType; } @@ -114,6 +115,13 @@ export interface AreaGeometry { clippedRanges: ClippedRanges; } +export interface BubbleGeometry { + points: PointGeometry[]; + color: Color; + seriesIdentifier: XYChartSeriesIdentifier; + seriesPointStyle: PointStyle; +} + export interface ArcGeometry { arc: string; color: Color; diff --git a/src/utils/themes/dark_theme.ts b/src/utils/themes/dark_theme.ts index f330c79e9b..14f7331ba0 100644 --- a/src/utils/themes/dark_theme.ts +++ b/src/utils/themes/dark_theme.ts @@ -43,6 +43,15 @@ export const DARK_THEME: Theme = { opacity: 1, }, }, + bubbleSeriesStyle: { + point: { + visible: true, + strokeWidth: 1, + fill: 'black', + radius: 2, + opacity: 1, + }, + }, areaSeriesStyle: { area: { visible: true, diff --git a/src/utils/themes/light_theme.ts b/src/utils/themes/light_theme.ts index 0ec8b814cd..53e452b578 100644 --- a/src/utils/themes/light_theme.ts +++ b/src/utils/themes/light_theme.ts @@ -42,6 +42,15 @@ export const LIGHT_THEME: Theme = { opacity: 1, }, }, + bubbleSeriesStyle: { + point: { + visible: true, + strokeWidth: 1, + fill: 'white', + radius: 2, + opacity: 1, + }, + }, areaSeriesStyle: { area: { visible: true, diff --git a/src/utils/themes/theme.ts b/src/utils/themes/theme.ts index e1ec9b1810..86db899cbd 100644 --- a/src/utils/themes/theme.ts +++ b/src/utils/themes/theme.ts @@ -168,6 +168,14 @@ export interface Theme { * You may use `SeriesColorAccessor` to assign colors to a given series or replace the `theme.colors.vizColors` colors to your desired colors. */ barSeriesStyle: BarSeriesStyle; + /** + * Global bubble styles. + * + * __Note:__ This is not used to set the color of a specific series. As such, any changes to the styles will not be reflected in the tooltip, legend, etc.. + * + * You may use `SeriesColorAccessor` to assign colors to a given series or replace the `theme.colors.vizColors` colors to your desired colors. + */ + bubbleSeriesStyle: BubbleSeriesStyle; arcSeriesStyle: ArcSeriesStyle; sharedStyle: SharedGeometryStateStyle; axes: AxisConfig; @@ -175,6 +183,12 @@ export interface Theme { colors: ColorConfig; legend: LegendStyle; crosshair: CrosshairStyle; + /** + * Used to scale radius with `markSizeAccessor` + * + * value from 1 to 100 + */ + markSizeRatio?: number; } export type PartialTheme = RecursivePartial; @@ -265,6 +279,10 @@ export interface BarSeriesStyle { displayValue: DisplayValueStyle; } +export interface BubbleSeriesStyle { + point: PointStyle; +} + export interface LineSeriesStyle { line: LineStyle; point: PointStyle; diff --git a/stories/axes/7_many_tick_labels.tsx b/stories/axes/7_many_tick_labels.tsx index 7de86f4fbb..f440e793bb 100644 --- a/stories/axes/7_many_tick_labels.tsx +++ b/stories/axes/7_many_tick_labels.tsx @@ -25,6 +25,7 @@ import { SeededDataGenerator } from '../../src/mocks/utils'; export const example = () => { const dg = new SeededDataGenerator(); const data = dg.generateSimpleSeries(31); + const customStyle = { tickLabelPadding: number('Tick Label Padding', 0), }; diff --git a/stories/bubble/1_simple.tsx b/stories/bubble/1_simple.tsx new file mode 100644 index 0000000000..2eea9f4281 --- /dev/null +++ b/stories/bubble/1_simple.tsx @@ -0,0 +1,77 @@ +/* + * 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 { number, boolean } from '@storybook/addon-knobs'; + +import { Axis, Chart, BubbleSeries, Position, ScaleType, Settings, TooltipType } from '../../src'; +import { SeededDataGenerator } from '../../src/mocks/utils'; +import { action } from '@storybook/addon-actions'; + +const dg = new SeededDataGenerator(); +const data = dg.generateRandomSeries(100); + +export const example = () => { + const onElementListeners = { + onElementClick: action('onElementClick'), + onElementOver: action('onElementOver'), + onElementOut: action('onElementOut'), + }; + const markSizeRatio = number('markSizeRatio', 30, { + range: true, + min: 1, + max: 100, + step: 1, + }); + const size = number('total points', 20, { + range: true, + min: 10, + max: 100, + step: 10, + }); + + return ( + + 20 / r} + {...onElementListeners} + /> + + Number(d).toFixed(2)} /> + + + + ); +}; diff --git a/stories/bubble/2_ordinal.tsx b/stories/bubble/2_ordinal.tsx new file mode 100644 index 0000000000..cef609c048 --- /dev/null +++ b/stories/bubble/2_ordinal.tsx @@ -0,0 +1,82 @@ +/* + * 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 { number, boolean } from '@storybook/addon-knobs'; + +import { Axis, Chart, BubbleSeries, Position, ScaleType, Settings, TooltipType } from '../../src'; +import { getRandomNumberGenerator } from '../../src/mocks/utils'; +import { action } from '@storybook/addon-actions'; + +const rng = getRandomNumberGenerator(); +const data = new Array(100).fill(0).map(() => { + return { + x: String.fromCharCode(rng(97, 122)), + y: rng(0, 100), + z: rng(0, 100), + }; +}); + +export const example = () => { + const onElementListeners = { + onElementClick: action('onElementClick'), + onElementOver: action('onElementOver'), + onElementOut: action('onElementOut'), + }; + const markSizeRatio = number('markSizeRatio', 30, { + range: true, + min: 1, + max: 100, + step: 1, + }); + const size = number('total points', 30, { + range: true, + min: 10, + max: 100, + step: 10, + }); + + return ( + + 20 / r} + {...onElementListeners} + /> + + Number(d).toFixed(2)} /> + + + + ); +}; diff --git a/stories/bubble/3_multiple.tsx b/stories/bubble/3_multiple.tsx new file mode 100644 index 0000000000..5caf587032 --- /dev/null +++ b/stories/bubble/3_multiple.tsx @@ -0,0 +1,78 @@ +/* + * 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 { number, boolean } from '@storybook/addon-knobs'; + +import { Axis, Chart, BubbleSeries, Position, ScaleType, Settings, TooltipType } from '../../src'; +import { SeededDataGenerator } from '../../src/mocks/utils'; +import { action } from '@storybook/addon-actions'; + +const dg = new SeededDataGenerator(); + +export const example = () => { + const onElementListeners = { + onElementClick: action('onElementClick'), + onElementOver: action('onElementOver'), + onElementOut: action('onElementOut'), + }; + const markSizeRatio = number('markSizeRatio', 30, { + range: true, + min: 1, + max: 100, + step: 1, + }); + const size = number('total points', 20, { + range: true, + min: 10, + max: 50, + step: 5, + }); + const data = dg.generateRandomGroupedSeries(size, 4); + + return ( + + 20 / r} + {...onElementListeners} + /> + + Number(d).toFixed(2)} /> + + + + ); +}; diff --git a/stories/bubble/4_mixed.tsx b/stories/bubble/4_mixed.tsx new file mode 100644 index 0000000000..1b74f1e607 --- /dev/null +++ b/stories/bubble/4_mixed.tsx @@ -0,0 +1,89 @@ +/* + * 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 { number, boolean } from '@storybook/addon-knobs'; + +import { Axis, Chart, BubbleSeries, Position, ScaleType, Settings, LineSeries } from '../../src'; +import { SeededDataGenerator, getRandomNumberGenerator } from '../../src/mocks/utils'; +import { action } from '@storybook/addon-actions'; + +const dg = new SeededDataGenerator(); +const rng = getRandomNumberGenerator(); +const lineData = dg.generateGroupedSeries(100, 2); +const bubbleData = new Array(100).fill(0).map((_, i) => ({ + x: i, + y: rng(0, 10), + z: rng(0, 100), +})); + +export const example = () => { + const onElementListeners = { + onElementClick: action('onElementClick'), + onElementOver: action('onElementOver'), + onElementOut: action('onElementOut'), + }; + const markSizeRatio = number('markSizeRatio', 30, { + range: true, + min: 1, + max: 100, + step: 1, + }); + + return ( + + 20 / r} + {...onElementListeners} + /> + + Number(d).toFixed(2)} /> + + + + + ); +}; + +example.text = 'testing'; diff --git a/stories/bubble/mixed.stories.tsx b/stories/bubble/mixed.stories.tsx new file mode 100644 index 0000000000..2e34cf97d3 --- /dev/null +++ b/stories/bubble/mixed.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_SOURCE_PANEL } from '../utils/storybook'; + +export default { + title: 'Bubble Chart (@alpha)', + parameters: { + options: { selectedPanel: SB_SOURCE_PANEL }, + }, +}; + +export { example as simple } from './1_simple'; +export { example as ordinal } from './2_ordinal'; +export { example as multiple } from './3_multiple'; +export { example as mixed } from './4_mixed'; diff --git a/stories/mixed/7_marks.tsx b/stories/mixed/7_marks.tsx new file mode 100644 index 0000000000..a556add46b --- /dev/null +++ b/stories/mixed/7_marks.tsx @@ -0,0 +1,95 @@ +/* + * 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 { number, boolean } from '@storybook/addon-knobs'; + +import { AreaSeries, Axis, Chart, LineSeries, Position, ScaleType, Settings } from '../../src'; +import { getRandomNumberGenerator } from '../../src/mocks/utils'; +import { action } from '@storybook/addon-actions'; + +const getRandomNumber = getRandomNumberGenerator(); +const data1 = new Array(100).fill(0).map((_, x) => ({ + x, + y: getRandomNumber(0, 100), + z: getRandomNumber(0, 50), +})); +const data2 = new Array(100).fill(0).map((_, x) => ({ + x, + y: getRandomNumber(0, 100), + z: getRandomNumber(0, 50), +})); + +export const example = () => { + const onElementListeners = { + onElementClick: action('onElementClick'), + onElementOver: action('onElementOver'), + onElementOut: action('onElementOut'), + }; + const markSizeRatio = number('markSizeRatio', 30, { + range: true, + min: 1, + max: 100, + step: 1, + }); + const size = number('data size', 20, { + range: true, + min: 10, + max: 100, + step: 10, + }); + + return ( + + 20 / r} + {...onElementListeners} + /> + + Number(d).toFixed(2)} /> + + + + + ); +}; diff --git a/stories/mixed/mixed.stories.tsx b/stories/mixed/mixed.stories.tsx index 17220ee97c..f3a74d0c8b 100644 --- a/stories/mixed/mixed.stories.tsx +++ b/stories/mixed/mixed.stories.tsx @@ -31,3 +31,4 @@ export { example as areasAndBars } from './3_areas_and_bars'; export { example as testBarLinesLinear } from './4_test_bar'; export { example as testBarLinesTime } from './5_test_bar_time'; export { example as fittingFunctionsNonStackedSeries } from './6_fitting'; +export { example as markSizeAccessor } from './7_marks'; diff --git a/tsconfig.lib.json b/tsconfig.lib.json index 0f24550fa5..b37e418a03 100644 --- a/tsconfig.lib.json +++ b/tsconfig.lib.json @@ -5,5 +5,5 @@ }, "extends": "./tsconfig", "include": ["src/**/*"], - "exclude": ["**/*.test.*", "**/__mocks__"] + "exclude": ["**/*.test.*", "**/__mocks__", "src/mocks"] }