From 8aac07c99192d910adc6bdd490f0dc8db420bf69 Mon Sep 17 00:00:00 2001
From: Martin Lassen <37809937+martin-ebay@users.noreply.github.com>
Date: Tue, 10 Jan 2023 10:15:58 -0800
Subject: [PATCH] highcharts charts (#1833)
---
LICENSE.txt | 8 +
README.md | 8 +
package-lock.json | 11 +
package.json | 1 +
src/common/charts/bar-chart.js | 131 +++
src/common/charts/legend.js | 19 +
src/common/charts/shared.js | 79 ++
src/common/event-utils/index.js | 11 +
src/components/ebay-area-chart/README.md | 10 +
.../ebay-area-chart/area-chart.stories.js | 137 +++
src/components/ebay-area-chart/component.js | 347 ++++++++
.../ebay-area-chart/examples/data.json | 777 ++++++++++++++++++
src/components/ebay-area-chart/index.marko | 22 +
src/components/ebay-area-chart/marko-tag.json | 22 +
src/components/ebay-area-chart/style.less | 23 +
src/components/ebay-bar-chart/README.md | 10 +
.../ebay-bar-chart/bar-chart.stories.js | 229 ++++++
src/components/ebay-bar-chart/component.js | 359 ++++++++
.../ebay-bar-chart/examples/data.json | 632 ++++++++++++++
src/components/ebay-bar-chart/index.marko | 20 +
src/components/ebay-bar-chart/marko-tag.json | 23 +
src/components/ebay-bar-chart/style.less | 15 +
.../ebay-bar-chart/subtemplate.marko | 16 +
src/components/ebay-line-chart/README.md | 10 +
src/components/ebay-line-chart/component.js | 424 ++++++++++
.../ebay-line-chart/examples/data.json | 757 +++++++++++++++++
src/components/ebay-line-chart/index.marko | 21 +
.../ebay-line-chart/line-chart.stories.js | 291 +++++++
src/components/ebay-line-chart/marko-tag.json | 23 +
src/components/ebay-line-chart/style.less | 31 +
src/components/ebay-line-chart/tooltip.marko | 13 +
src/components/ebay-spark-line/README.md | 10 +
src/components/ebay-spark-line/component.js | 56 ++
.../ebay-spark-line/examples/data.json | 136 +++
src/components/ebay-spark-line/index.marko | 16 +
.../ebay-spark-line/spark-line.stories.js | 67 ++
src/components/ebay-spark-line/style.js | 1 +
src/components/ebay-spark-line/style.less | 25 +
.../renders-a-blue-line.expected.html | 17 +
.../renders-a-green-line.expected.html | 17 +
.../renders-a-red-line.expected.html | 17 +
.../ebay-spark-line/test/test.server.js | 22 +
42 files changed, 4864 insertions(+)
create mode 100644 src/common/charts/bar-chart.js
create mode 100644 src/common/charts/legend.js
create mode 100644 src/common/charts/shared.js
create mode 100644 src/components/ebay-area-chart/README.md
create mode 100644 src/components/ebay-area-chart/area-chart.stories.js
create mode 100644 src/components/ebay-area-chart/component.js
create mode 100644 src/components/ebay-area-chart/examples/data.json
create mode 100644 src/components/ebay-area-chart/index.marko
create mode 100644 src/components/ebay-area-chart/marko-tag.json
create mode 100644 src/components/ebay-area-chart/style.less
create mode 100644 src/components/ebay-bar-chart/README.md
create mode 100644 src/components/ebay-bar-chart/bar-chart.stories.js
create mode 100644 src/components/ebay-bar-chart/component.js
create mode 100644 src/components/ebay-bar-chart/examples/data.json
create mode 100644 src/components/ebay-bar-chart/index.marko
create mode 100644 src/components/ebay-bar-chart/marko-tag.json
create mode 100644 src/components/ebay-bar-chart/style.less
create mode 100644 src/components/ebay-bar-chart/subtemplate.marko
create mode 100644 src/components/ebay-line-chart/README.md
create mode 100644 src/components/ebay-line-chart/component.js
create mode 100644 src/components/ebay-line-chart/examples/data.json
create mode 100644 src/components/ebay-line-chart/index.marko
create mode 100644 src/components/ebay-line-chart/line-chart.stories.js
create mode 100644 src/components/ebay-line-chart/marko-tag.json
create mode 100644 src/components/ebay-line-chart/style.less
create mode 100644 src/components/ebay-line-chart/tooltip.marko
create mode 100644 src/components/ebay-spark-line/README.md
create mode 100644 src/components/ebay-spark-line/component.js
create mode 100644 src/components/ebay-spark-line/examples/data.json
create mode 100644 src/components/ebay-spark-line/index.marko
create mode 100644 src/components/ebay-spark-line/spark-line.stories.js
create mode 100644 src/components/ebay-spark-line/style.js
create mode 100644 src/components/ebay-spark-line/style.less
create mode 100644 src/components/ebay-spark-line/test/__snapshots__/renders-a-blue-line.expected.html
create mode 100644 src/components/ebay-spark-line/test/__snapshots__/renders-a-green-line.expected.html
create mode 100644 src/components/ebay-spark-line/test/__snapshots__/renders-a-red-line.expected.html
create mode 100644 src/components/ebay-spark-line/test/test.server.js
diff --git a/LICENSE.txt b/LICENSE.txt
index fdf8e6886..b51d2d6be 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -19,3 +19,11 @@ 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.
+
+USE OF SOME COMPONENTS REQUIRES A SEPARATE, NON-OPEN-SOURCE LICENSE FROM THIRD PARTIES
+
+The data visualization components and the charting components of the eBayUI library are designed to use one or more HighCharts® software products. HighCharts® is a registered trademark of HighSoft AS. HighSoft AS is not affiliated with Ebay. Ebay provides no warranties of any kind (e.g., of merchantability, fitness for a particular purpose, and noninfringement), whether express or implied, with respect to the HighCharts® software products that the data visualization components and the charting components are designed to use.
+
+COMMERCIAL USE OF HIGHCHARTS® SOFTWARE PRODUCTS REQUIRES A PAID LICENSE PROVIDED BY HIGHSOFT AS. While many components of the eBayUI library are licensed under the MIT License, the HighCharts® software products which the data visualization components and charting components of the EbayUI library are designed to use are NOT licensed under the MIT License or any other open source license. Rights pertaining to HighCharts® software products (e.g., including, but not limited to, rights to use, install, distribute, publish, merge, duplicate, and modify) are governed by the terms of one or more proprietary license agreements that are available online at http://www.highcharts.com or by the terms of custom license agreements that HighSoft AS may negotiate with its customers at its own discretion. While HighSoft AS may choose to license HighCharts® software products for non-commercial use at no cost, IT IS THE RESPONSIBILITY OF ANY PARTY THAT WISHES TO USE HIGHCHARTS® SOFTWARE PRODUCTS TO VERIFY THE TERMS OF SUCH A LICENSE WITH HIGHSOFT AS. NOTWITHSTANDING ANY PROVISION OF THIS LICENSE, PARTIES WHO ARE NOT LICENSED BY HIGHSOFT AS (OR ITS SUCCESSORS OR ASSIGNS) TO USE HIGHCHARTS® SOFTWARE PRODUCTS ARE NOT LICENSED TO USE THE DATA VISUALIZATION COMPONENTS AND THE CHARTING COMPONENTS OF THE EBAYUI LIBRARY.
+
+This notice shall be included in all copies or substantial portions of the Software.
\ No newline at end of file
diff --git a/README.md b/README.md
index 107fe8ee2..a668b8b18 100644
--- a/README.md
+++ b/README.md
@@ -234,3 +234,11 @@ Looking to contribute to eBay UI? Please visit our [contributing page](CONTRIBUT
Copyright (c) 2018 eBay Inc.
Use of this source code is governed by a MIT-style license that can be found in the LICENSE file or at [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT).
+
+USE OF SOME COMPONENTS REQUIRES A SEPARATE, NON-OPEN-SOURCE LICENSE FROM THIRD PARTIES
+
+The data visualization components and the charting components of the eBayUI library are designed to use one or more HighCharts® software products. HighCharts® is a registered trademark of HighSoft AS. HighSoft AS is not affiliated with Ebay. Ebay provides no warranties of any kind (e.g., of merchantability, fitness for a particular purpose, and noninfringement), whether express or implied, with respect to the HighCharts® software products that the data visualization components and the charting components are designed to use.
+
+COMMERCIAL USE OF HIGHCHARTS® SOFTWARE PRODUCTS REQUIRES A PAID LICENSE PROVIDED BY HIGHSOFT AS. While many components of the eBayUI library are licensed under the MIT License, the HighCharts® software products which the data visualization components and charting components of the EbayUI library are designed to use are NOT licensed under the MIT License or any other open source license. Rights pertaining to HighCharts® software products (e.g., including, but not limited to, rights to use, install, distribute, publish, merge, duplicate, and modify) are governed by the terms of one or more proprietary license agreements that are available online at http://www.highcharts.com or by the terms of custom license agreements that HighSoft AS may negotiate with its customers at its own discretion. While HighSoft AS may choose to license HighCharts® software products for non-commercial use at no cost, IT IS THE RESPONSIBILITY OF ANY PARTY THAT WISHES TO USE HIGHCHARTS® SOFTWARE PRODUCTS TO VERIFY THE TERMS OF SUCH A LICENSE WITH HIGHSOFT AS. NOTWITHSTANDING ANY PROVISION OF THIS LICENSE, PARTIES WHO ARE NOT LICENSED BY HIGHSOFT AS (OR ITS SUCCESSORS OR ASSIGNS) TO USE HIGHCHARTS® SOFTWARE PRODUCTS ARE NOT LICENSED TO USE THE DATA VISUALIZATION COMPONENTS AND THE CHARTING COMPONENTS OF THE EBAYUI LIBRARY.
+
+This notice shall be included in all copies or substantial portions of the Software.
diff --git a/package-lock.json b/package-lock.json
index 4735884f3..0042b6867 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"license": "MIT",
"dependencies": {
"@marko-tags/subscribe": "^0.4.2",
+ "highcharts": "^10.2.1",
"makeup-active-descendant": "0.6.1",
"makeup-expander": "~0.10.1",
"makeup-floating-label": "~0.3.2",
@@ -23774,6 +23775,11 @@
"he": "bin/he"
}
},
+ "node_modules/highcharts": {
+ "version": "10.2.1",
+ "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-10.2.1.tgz",
+ "integrity": "sha512-4QwLQwWc0XdBHXc2Uy6IJisAUir+sgQIMyFqYZc3BD9iFSFUdllLkRyoed6y33aPb73LAMZE2qLB5SuLqFE/fg=="
+ },
"node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
@@ -58510,6 +58516,11 @@
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
+ "highcharts": {
+ "version": "10.2.1",
+ "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-10.2.1.tgz",
+ "integrity": "sha512-4QwLQwWc0XdBHXc2Uy6IJisAUir+sgQIMyFqYZc3BD9iFSFUdllLkRyoed6y33aPb73LAMZE2qLB5SuLqFE/fg=="
+ },
"highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
diff --git a/package.json b/package.json
index 23f29be5d..5d5537171 100644
--- a/package.json
+++ b/package.json
@@ -137,6 +137,7 @@
},
"dependencies": {
"@marko-tags/subscribe": "^0.4.2",
+ "highcharts": "^10.2.1",
"makeup-active-descendant": "0.6.1",
"makeup-expander": "~0.10.1",
"makeup-floating-label": "~0.3.2",
diff --git a/src/common/charts/bar-chart.js b/src/common/charts/bar-chart.js
new file mode 100644
index 000000000..c5b86028f
--- /dev/null
+++ b/src/common/charts/bar-chart.js
@@ -0,0 +1,131 @@
+export function eBayColumns(HighCharts) {
+ if (!HighCharts.seriesTypes.column.prototype.ebayColumn) {
+ // check if the column has been extended before attempting to extend again
+ HighCharts.wrap(HighCharts.seriesTypes.column.prototype, 'translate', function (proceed) {
+ // set a flag that can be checked so the prototype isn't overwritten twice, which looses the original code that is called with the proceed function
+ HighCharts.seriesTypes.column.prototype.ebayColumn = true;
+ const top = this.options.top, // pull out the top value from the highcharts options object
+ bottom = this.options.bottom; // pull out the bottom value from the highcarts options object
+
+ // this runs the original code for this translate function at this point
+ // if it is not run HighCharts.each will not exist yet
+ proceed.call(this);
+ HighCharts.each(this.points, (point) => {
+ // loop over each data point element
+ const shapeArgs = point.shapeArgs, // reference to the points shapeArgs object
+ x = shapeArgs.x, // references to the shapeArgs X value
+ w = shapeArgs.width; // references to the shapeArgs width value
+
+ let y = shapeArgs.y, // references to the shapeArgs X value
+ // references to the shapeArgs height value.
+ // If it is not marked as a bottom point subract 4 pixels to create the visual gap in the chart
+ h = shapeArgs.height - (bottom ? 0 : 4);
+
+ // check to make sure h is not negative and if it is set the hight back to the original height and move it's y position instead
+ if (h < 0) {
+ h = shapeArgs.height;
+ y = y - 4;
+ }
+
+ const cornerRadius = 3;
+
+ // HighCharts.relativeLength returns a length based on either the integer value, or a percentage of a base with w being the base.
+ let rTopLeft = HighCharts.relativeLength(top ? cornerRadius : 0, w),
+ rTopRight = HighCharts.relativeLength(top ? cornerRadius : 0, w),
+ rBottomRight = HighCharts.relativeLength(bottom ? cornerRadius : 0, w),
+ rBottomLeft = HighCharts.relativeLength(bottom ? cornerRadius : 0, w);
+
+ // max corner radius is half the width and height of the shape
+ const maxCornerRadius = Math.min(w, h) / 2;
+
+ // adjust top left corner if it is larger that maxCornerRadius
+ if (rTopLeft > maxCornerRadius) rTopLeft = maxCornerRadius;
+
+ // adjust top right corner if it is larger that maxCornerRadius
+ if (rTopRight > maxCornerRadius) rTopRight = maxCornerRadius;
+
+ // adjust bottom right corner if it is larger that maxCornerRadius
+ if (rBottomRight > maxCornerRadius) rBottomRight = maxCornerRadius;
+
+ // adjust bottom left corner if it is larger that maxCornerRadius
+ if (rBottomLeft > maxCornerRadius) rBottomLeft = maxCornerRadius;
+
+ point.dlBox = point.shapeArgs; // set the data label Box for aligning tooltips to match the point shape
+ point.shapeY = y; // set the points y position
+ point.shapeType = 'path'; // set the shape type to path
+ point.shapeArgs = {
+ // define the shape arg used to render the svg path element
+ // d is a standard SVG path definition string
+ // refer to https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
+ d: [
+ // move to the top left corner plus rTopLeft for the beveled corner width to start the path
+ 'M',
+ x + rTopLeft,
+ y,
+ // top side line
+ 'L',
+ x + w - rTopRight,
+ y,
+ // top right corner curve
+ 'C',
+ // top right corner start bezier control point
+ x + w - rTopRight / 2,
+ y,
+ // top right corner end bezier control point
+ x + w,
+ y + rTopRight / 2,
+ // top right
+ x + w,
+ y + rTopRight,
+ // right side
+ 'L',
+ x + w,
+ y + h - rBottomRight,
+ // bottom right corner
+ 'C',
+ // bottom right corner start bezier control point
+ x + w,
+ y + h - rBottomRight / 2,
+ // bottom right corner end bezier control point
+ x + w - rBottomRight / 2,
+ y + h,
+ // bottom right
+ x + w - rBottomRight,
+ y + h,
+ // bottom side
+ 'L',
+ x + rBottomLeft,
+ y + h,
+ // bottom left corner
+ 'C',
+ // bottom left corner start bezier control point
+ x + rBottomLeft / 2,
+ y + h,
+ // bottom left corner start bezier control point
+ x,
+ y + h - rBottomLeft / 2,
+ // bottom left
+ x,
+ y + h - rBottomLeft,
+ // left side
+ 'L',
+ x,
+ y + rTopLeft,
+ // top left corner
+ 'C',
+ // top left corner start bezier control point
+ x,
+ y + rTopLeft / 2,
+ // top left corner end bezier control point
+ x + rTopLeft / 2,
+ y,
+ // top left corner
+ x + rTopLeft,
+ y,
+ 'Z', // close path
+ ],
+ };
+ });
+ });
+ }
+}
diff --git a/src/common/charts/legend.js b/src/common/charts/legend.js
new file mode 100644
index 000000000..018d7fe67
--- /dev/null
+++ b/src/common/charts/legend.js
@@ -0,0 +1,19 @@
+export function ebayLegend(H) {
+ H.wrap(H.Legend.prototype, 'colorizeItem', function (p, item, visible) {
+ // this helps make the legend svg elements render crisper
+ const width = H.pick(item.borderWidth, 1),
+ crisp = -(width % 2) / 2;
+ p.apply(this, [].slice.call(arguments, 1));
+
+ if (item.legendSymbol) {
+ if (visible) {
+ item.legendSymbol.attr({
+ 'stroke-width': width, // set the border width if visible
+ translateX: crisp, // set translateX to land on a perfect pixel
+ translateY: crisp, // set translateX to land on a perfect pixel
+ stroke: item.options.borderColor, // set the border color of legend item
+ });
+ }
+ }
+ });
+}
diff --git a/src/common/charts/shared.js b/src/common/charts/shared.js
new file mode 100644
index 000000000..c824c3473
--- /dev/null
+++ b/src/common/charts/shared.js
@@ -0,0 +1,79 @@
+export const chartFontFamily = '"Market Sans", Arial, sans-serif',
+ backgroundColor = 'var(--color-background-primary)',
+ gridColor = 'var(--color-data-viz-grid)',
+ labelsColor = 'var(--color-data-viz-labels)',
+ legendColor = 'var(--color-data-viz-legend)',
+ legendInactiveColor = 'var(--color-data-viz-legend-inactive)',
+ legendHoverColor = 'var(--color-data-viz-legend-hover)',
+ tooltipBackgroundColor = 'var(--color-neutral-0)',
+ tooltipShadows =
+ 'drop-shadow(0 2px 7px var(--color-data-viz-tooltip-shadow-primary)) drop-shadow(0 5px 7px var(--color-data-viz-tooltip-shadow-secondary))',
+ lineChartPrimaryColor = 'var(--color-data-viz-line-chart-primary)',
+ lineChartSecondaryColor = 'var(--color-data-viz-line-chart-secondary)',
+ lineChartTertiaryColor = 'var(--color-data-viz-line-chart-tertiary)',
+ lineChartQueternaryColor = 'var(--color-data-viz-line-chart-queternary)',
+ lineChartQuinaryColor = 'var(--color-data-viz-line-chart-quinary)',
+ trendPositiveColor = 'var(--color-data-viz-trend-positive)',
+ trendNegativeColor = 'var(--color-data-viz-trend-negative)',
+ chartPrimaryColor = 'var(--color-data-viz-chart-primary)',
+ chartSecondaryColor = 'var(--color-data-viz-chart-secondary)',
+ chartTertiaryBackgroundColor = 'var(--color-data-viz-chart-tertiary-background)',
+ chartTertiaryStrokeColor = 'var(--color-data-viz-chart-tertiary-stroke)',
+ chartQuaternaryBackgroundColor = 'var(--color-data-viz-chart-quaternary-background)',
+ chartQuaternaryStrokeColor = 'var(--color-data-viz-chart-quaternary-stroke)',
+ chartQuinaryBackgroundColor = 'var(--color-data-viz-chart-quinary-background)',
+ chartQuinaryStrokeColor = 'var(--color-data-viz-chart-quinary-stroke)',
+ // patterns are in highcharts PatternOptionsObject format
+ // refer to https://api.highcharts.com/class-reference/Highcharts.PatternOptionsObject
+ patternTertiary = {
+ pattern: {
+ path: {
+ // d is a standard SVG path definition string
+ // refer to https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
+ d: 'M0 0 L0 3', // draw a 3 until vertical line
+ },
+ width: 4.5, // defines the x bounds of the repeating pattern
+ height: 3, // defines the y bounds of the repeating pattern
+ backgroundColor: chartTertiaryBackgroundColor, // sets the patterns background color
+ color: chartTertiaryStrokeColor, // sets the patterns stroke color
+ patternTransform: 'rotate(-60)', // rotates the path -60 degrees
+ },
+ },
+ patternQuaternary = {
+ pattern: {
+ path: {
+ // d is a standard SVG path definition string
+ // refer to https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
+ d: 'M0 0 L3 0',
+ },
+ width: 3, // defines the x bounds of the repeating pattern
+ height: 5, // defines the y bounds of the repeating pattern
+ backgroundColor: chartQuaternaryBackgroundColor, // sets the patterns background color
+ color: chartQuaternaryStrokeColor, // sets the patterns stroke color
+ },
+ },
+ colorMapping = [
+ chartPrimaryColor,
+ chartSecondaryColor,
+ patternTertiary,
+ patternQuaternary,
+ chartQuinaryBackgroundColor,
+ ],
+ // function is used to set up the colors including lineColor(svg stroke) on each of the series objects
+ // based on the length of the series array
+ setSeriesColors = function (series) {
+ const strokeColorMapping = [
+ chartPrimaryColor,
+ chartSecondaryColor,
+ chartTertiaryStrokeColor,
+ chartQuaternaryStrokeColor,
+ chartQuaternaryStrokeColor,
+ ];
+
+ for (let i = 0; i < series.length; i++) {
+ // Added a modulus in case the user passes in more than 5 series so it doesn't error out
+ const color = strokeColorMapping[i % strokeColorMapping.length];
+ series[i].lineColor = color;
+ series[i].borderColor = color;
+ }
+ };
diff --git a/src/common/event-utils/index.js b/src/common/event-utils/index.js
index 6a68706b2..66f00a062 100644
--- a/src/common/event-utils/index.js
+++ b/src/common/event-utils/index.js
@@ -98,7 +98,18 @@ const resizeUtil = {
removeEventListener,
};
+function debounce(func, timeout = 100) {
+ let timer;
+ return (...args) => {
+ clearTimeout(timer);
+ timer = setTimeout(() => {
+ func.apply(this, args);
+ }, timeout);
+ };
+}
+
export {
+ debounce,
handleEnterKeydown,
handleActionKeydown,
handleEscapeKeydown,
diff --git a/src/components/ebay-area-chart/README.md b/src/components/ebay-area-chart/README.md
new file mode 100644
index 000000000..779c8170d
--- /dev/null
+++ b/src/components/ebay-area-chart/README.md
@@ -0,0 +1,10 @@
+
+
+ ebay-area-chart
+
+
+ DS v3.7.0
+
+
+
+The area chart displays one to five series of data points as an interactive stacked area chart
diff --git a/src/components/ebay-area-chart/area-chart.stories.js b/src/components/ebay-area-chart/area-chart.stories.js
new file mode 100644
index 000000000..f43caa5b5
--- /dev/null
+++ b/src/components/ebay-area-chart/area-chart.stories.js
@@ -0,0 +1,137 @@
+import { tagToString } from '../../../.storybook/storybook-code-source';
+import { addRenderBodies } from '../../../.storybook/utils';
+import Readme from './README.md';
+import Component from './index.marko';
+import * as sampleSeriesData from './examples/data.json';
+
+const Template = (args) => ({
+ input: addRenderBodies(args),
+});
+
+export default {
+ title: 'charts/ebay-area-chart',
+ excludeStories: '.*',
+ component: Component,
+ parameters: {
+ docs: {
+ description: {
+ component: Readme,
+ },
+ },
+ },
+ argTypes: {
+ title: {
+ type: { name: 'string', required: false },
+ description: 'A title displayed above the graph',
+ },
+ description: {
+ type: { name: 'string', required: true },
+ description: 'A description of what the chart is displaying',
+ },
+ series: {
+ type: { name: 'object', required: true },
+ description:
+ 'The series is an array of one to five arrays of point objects, each point contains an `x`, `y`, and `label`. `x` is an epoch/unix time code, `y` is a numeric value, `label` is what is displayed for the `y` value in the tool tip',
+ },
+ xAxisLabelFormat: {
+ type: { name: 'string', required: false },
+ description:
+ 'Used to modify the display of the x-axis labels. Accepts a string like `{value:%Y-%m-%d}`. Refer to https://api.highcharts.com/class-reference/Highcharts.Time#dateFormat for available format keys',
+ table: {
+ defaultValue: {
+ summary: '{value:%b %e}',
+ },
+ },
+ },
+ xAxisPositioner: {
+ type: { name: 'function', required: false },
+ description:
+ 'A custom function that returns an array of epoch/unix time values where x-axis labels will be displayed. You can access `this.dataMin` and `this.dataMax` from the function to help determine positions.',
+ },
+ yAxisLabels: {
+ type: { name: 'array', required: false },
+ description:
+ 'An array of labels to use on the y-axis. Use in conjunction with yAxisPositioner. Make sure the length of the yAxisLabels match the length of the positions array returned by the yAxisPositioner function',
+ },
+ yAxisPositioner: {
+ type: { name: 'function', required: false },
+ description:
+ 'A custom function that returns an array of numeric values where y-axis labels will be displayed. You can access `this.dataMin` and `this.dataMax` from the function to help determine positions',
+ },
+ class: {
+ type: { name: 'string', require: false },
+ description: 'A class name that will be added to the main chart container',
+ },
+ },
+};
+
+export const Standard = Template.bind({});
+Standard.args = {
+ title: 'Single series sample area chart',
+ description: 'this chart displays 30 days of sample values',
+ series: sampleSeriesData.slice(0, 1),
+};
+Standard.parameters = {
+ docs: {
+ source: {
+ code: tagToString('bar-chart', Standard.args),
+ },
+ },
+};
+
+export const TwoSeries = Template.bind({});
+TwoSeries.args = {
+ title: 'Two series sample area chart',
+ description: 'this chart displays 30 days of values for sample1 and sample2',
+ series: sampleSeriesData.slice(0, 2),
+};
+TwoSeries.parameters = {
+ docs: {
+ source: {
+ code: tagToString('bar-chart', TwoSeries.args),
+ },
+ },
+};
+
+export const ThreeSeries = Template.bind({});
+ThreeSeries.args = {
+ title: 'Three series sample area chart',
+ description: 'this chart displays 30 days of values for sample1, sample2 and sample3',
+ series: sampleSeriesData.slice(0, 3),
+};
+ThreeSeries.parameters = {
+ docs: {
+ source: {
+ code: tagToString('bar-chart', ThreeSeries.args),
+ },
+ },
+};
+
+export const FourSeries = Template.bind({});
+FourSeries.args = {
+ title: 'Four series sample area chart',
+ description: 'this chart displays 30 days of values for sample1, sample2, sample3, and sample4',
+ series: sampleSeriesData.slice(0, 4),
+};
+FourSeries.parameters = {
+ docs: {
+ source: {
+ code: tagToString('bar-chart', FourSeries.args),
+ },
+ },
+};
+
+export const FiveSeries = Template.bind({});
+FiveSeries.args = {
+ title: 'Five series sample area chart',
+ description:
+ 'this chart displays 30 days of values for sample1, sample2, sample3, sample4, and sample5',
+ series: sampleSeriesData,
+};
+FiveSeries.parameters = {
+ docs: {
+ source: {
+ code: tagToString('bar-chart', FiveSeries.args),
+ },
+ },
+};
diff --git a/src/components/ebay-area-chart/component.js b/src/components/ebay-area-chart/component.js
new file mode 100644
index 000000000..6f9fc2523
--- /dev/null
+++ b/src/components/ebay-area-chart/component.js
@@ -0,0 +1,347 @@
+import Highcharts from 'highcharts';
+import accessibility from 'highcharts/modules/accessibility';
+import patternFill from 'highcharts/modules/pattern-fill';
+import {
+ chartFontFamily,
+ backgroundColor,
+ gridColor,
+ labelsColor,
+ legendColor,
+ legendInactiveColor,
+ legendHoverColor,
+ tooltipBackgroundColor,
+ tooltipShadows,
+ setSeriesColors,
+ colorMapping,
+} from '../../common/charts/shared';
+import { debounce } from '../../common/event-utils';
+import { ebayLegend } from '../../common/charts/legend';
+
+const pointSize = 1.5;
+
+export default class {
+ onMount() {
+ this._initializeHighchartExtensions();
+ this._setupEvents();
+ this._setupCharts();
+ }
+ onInput() {
+ // if chartRef does not exist do not try to run setupCharts as it may be server side and highcharts only works on the client side
+ if (this.chartRef && this.chartRef.destroy) {
+ this.chartRef.destroy();
+ this._setupCharts();
+ }
+ }
+ getContainerId() {
+ return `ebay-bar-chart-${this.id}`;
+ }
+ _initializeHighchartExtensions() {
+ // enable highcharts accessibility with wrapper function
+ accessibility(Highcharts);
+ // patternFill highcharts wrapper function enables rendering patterns instead of just solid colors
+ patternFill(Highcharts);
+ // add custom legend wrapper function
+ ebayLegend(Highcharts);
+ }
+ _setupEvents() {
+ // bind functions to keep scope and setup debounced versions of function calls
+ this.debounce = debounce.bind(this);
+ this.handleMouseOver = this.handleMouseOver.bind(this);
+ this.handleMouseOut = this.handleMouseOut.bind(this);
+ this.mouseOut = this.debounce(() => this.handleMouseOut(), 80); // 80ms delay for debounce
+ this.mouseOver = this.debounce((e) => this.handleMouseOver(e), 85); // 85ms delay for debounce so it doesn't colide with mouseOut debounce calls
+ }
+ _setupCharts() {
+ // check if a single series was passed in for series and if so add it to a new array
+ const series = Array.isArray(this.input.series) ? this.input.series : [this.input.series];
+
+ // update the zIndex of each series object so they render in the correct order
+ // and configure the markers that are displayed on hover
+ series.forEach((s, i) => {
+ s.zIndex = series.length - i;
+ s.marker = {
+ symbol: 'circle',
+ borderWidth: 3,
+ };
+ });
+ setSeriesColors(series);
+
+ const config = {
+ title: this.getTitleConfig(),
+ chart: this.getChartConfig(),
+ colors: colorMapping,
+ xAxis: this.getXAxisConfig(),
+ yAxis: this.getYAxisConfig(series),
+ legend: this.getLegendConfig(),
+ tooltip: this.getTooltipConfig(),
+ plotOptions: this.getPlotOptions(),
+ series, // pass in the configured series array
+ credits: {
+ enabled: false, // hide the highcharts label and link in the bottom right of chart
+ },
+ };
+ // initialize and keep reference to chart
+ this.chartRef = Highcharts.chart(this.getContainerId(), config);
+ }
+ getTitleConfig() {
+ return {
+ text: this.input.title,
+ align: 'left',
+ useHTML: true,
+ style: {
+ // styles are set in JS since they are rendered in the SVG
+ fontSize: '18px',
+ fontWeight: 700,
+ },
+ };
+ }
+ getChartConfig() {
+ return {
+ type: 'area',
+ backgroundColor: backgroundColor,
+ style: {
+ // styles are set in JS since they are rendered in the SVG
+ fontFamily: chartFontFamily,
+ },
+ };
+ }
+ getXAxisConfig() {
+ return {
+ // currently setup to support epoch time values for xAxisLabels.
+ // It is possible to set custom non datetime xAxisLabels but will need changes to this component
+ type: 'datetime',
+ labels: {
+ // input.xAxisLabelFormat allows overriding the default short month / day label
+ // refer to https://api.highcharts.com/class-reference/Highcharts.Time#dateFormat to customize
+ format: this.input.xAxisLabelFormat ? this.input.xAxisLabelFormat : '{value:%b %e}',
+ align: 'center',
+ style: {
+ color: labelsColor, // setting label colors
+ },
+ },
+ tickWidth: 0, // hide the vertical tick on xAxis labels
+ crosshair: {
+ zIndex: 3, // make sure the vertical crosshair line on hover shows up on top
+ },
+ tickPositioner: this.input.xAxisPositioner, // optional input to allow configuring the position of xAxis tick marks
+ };
+ }
+ getYAxisConfig(series) {
+ const component = this; // component reference used in formatter functions that don't have the same scope
+ let yLabelsItterator = 0; // used when yAxisLabels array is provided in input
+ let maxYAxisValue = 0; // use to determine the highest yAxis value
+ series.forEach((s) => {
+ maxYAxisValue = s.data.reduce((p, c) => (c > p ? c : p), maxYAxisValue);
+ });
+ return {
+ gridLineColor: gridColor, // sets the horizontal grid line colors
+ opposite: true, // moves yAxis labels to the right side of the chart
+ reversedStacks: false, // makes so series one starts at the bottom of the yAxis, by default this is true
+ labels: {
+ // if yAxisLabels are not passed in display the standard label
+ format: !this.input.yAxisLabels && '${text}',
+ // if yAxisLabels array is passed in this formatter function is needed to
+ // return the proper label for each yAxis tick mark
+ formatter:
+ this.input.yAxisLabels &&
+ function () {
+ if (this.isFirst) {
+ yLabelsItterator = -1;
+ }
+ yLabelsItterator = yLabelsItterator + 1;
+ return component.input.yAxisLabels[yLabelsItterator];
+ },
+ style: {
+ color: labelsColor, // setting label colors
+ },
+ },
+ maxVal: maxYAxisValue,
+ title: {
+ enabled: false, // hide the axis label next to the axis
+ },
+ offset: 0, // set to zero for no offset refer to https://api.highcharts.com/highcharts/yAxis.offset
+ // passed in function for yAxisPositioner refer to https://api.highcharts.com/highcharts/yAxis.tickPositioner for use
+ tickPositioner: this.input.yAxisPositioner,
+ };
+ }
+ getLegendConfig() {
+ return {
+ // if only a single series is provided do not display the legend
+ enabled: this.input.series.length > 1,
+ symbolRadius: 2, // corner radius on legend identifiers svg element
+ symbolWidth: 12, // setting the width of the legend identifiers svg element
+ symbolHeight: 12, // setting the height of the legend identifiers svg element
+ itemStyle: {
+ color: legendColor, // set the color of the text in the legend
+ },
+ itemHiddenStyle: {
+ color: legendInactiveColor, // set legend text color when legend item has been clicked and hidden
+ },
+ itemHoverStyle: {
+ color: legendHoverColor, // set legend text color on hover of legend element
+ },
+ };
+ }
+
+ getTooltipConfig() {
+ const component = this; // component reference used in formatter functions that don't have the same scope
+ return {
+ formatter: function () {
+ // refer to https://api.highcharts.com/class-reference/Highcharts.Time#dateFormat for dateFormat variables
+ // s is used to compile html string of formatted tooltip data
+ let s = `${Highcharts.dateFormat('%b %e, %Y', this.x, false)}`; // sets the displayed date at the top of the tooltip
+ if (component.chartRef.series.length > 1) {
+ // setup html for multi series tooltip
+ component.chartRef.series.forEach((serie) => {
+ // cycle through each series
+ serie.data.forEach((d) => {
+ // cycle through each series data array to match x value with active hovered xAxis position
+ if (d.x === this.x) {
+ // when the x value matches the hovered xAxis position
+ s += `
${serie.name}${d.label}
`;
+ }
+ });
+ });
+ } else {
+ // setup html for single series tooltip
+ // cycle through points of the single series and find the one that matches the active xAxis
+ this.points.forEach((d) => {
+ if (d.x === this.x) {
+ // when the x value matches the hovered xAxis position
+ s += `