@ebay/ebayui-core
Version:
Collection of core eBay components; considered to be the building blocks for all composite structures, pages & apps.
401 lines (400 loc) • 19.4 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const highcharts_1 = require("@internal/highcharts");
const shared_1 = require("../../common/charts/shared");
const event_utils_1 = require("../../common/event-utils");
const tooltip_marko_1 = __importDefault(require("./tooltip.marko"));
const pointSize = 6; // controls the size of the plot point markers on lines
class LineChart {
onCreate() {
this.axisTicksLength = -1;
}
handleError(err) {
this.emit("load-error", err);
}
handleSuccess() {
this._setupChart();
}
onMount() {
(0, highcharts_1.load)()
.then(() => {
this.handleSuccess();
})
.catch((e) => {
this.handleError(e);
});
}
onInput(input) {
this.input = this.input || input;
// 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._setupChart();
}
}
getContainerId() {
return `ebay-line-graph-${this.id}`;
}
_setupChart() {
const colors = [
// configure the array of colors to use for each series
shared_1.lineChartPrimaryColor,
shared_1.lineChartSecondaryColor,
shared_1.lineChartTertiaryColor,
shared_1.lineChartQueternaryColor,
shared_1.lineChartQuinaryColor,
];
// 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];
if (this.input.trend) {
// if the trend property exist check value and adjust the first color
const trend = typeof this.input.trend === "string" &&
this.input.trend.toLowerCase(); // if trend of type string force to lowercase
const isPositive = series[0].data[0].y <
series[0].data[series[0].data.length - 1].y; // auto trend positive check between first and last data values of the single series
if (trend === "positive" || // if "positive" is passed in for the trend property
(trend !== "negative" && trend !== "neutral" && isPositive) // if check if trend is does not equal negative or neutral, and if so use the auto positive calculation
) {
colors[0] = shared_1.trendPositiveColor; // set the color to the positive trend color
}
else if (trend === "negative" ||
(trend !== "neutral" && !isPositive)) {
// if the trend property equals negative, or trend does not equal neutral and isPositive is false
colors[0] = shared_1.trendNegativeColor; // set the negative trend color
}
}
// configure the symbol used for each series markers
series.forEach((s, i) => {
s.marker = {
symbol: this.getSymbol(i),
};
});
const chart = this.getChartConfig();
const xAxis = this.getXAxisConfig();
const yAxis = this.getYAxisConfig(series);
const legend = this.getLegendConfig();
const tooltip = this.getTooltipConfig();
const plotOptions = this.getPlotOptionsConfig(series);
const title = {
text: this.input.title,
align: "left",
useHTML: true,
style: {
fontSize: "18px",
fontWeight: "700",
},
};
const config = {
title,
chart,
colors,
xAxis,
yAxis,
legend,
tooltip,
plotOptions,
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
// eslint-disable-next-line no-undef,new-cap
this.chartRef = Highcharts.chart(this.getContainerId(), config);
// call update markers after the initial render to determine which markers to display if plotPoints is set to true
this.updateMarkers();
}
getSymbol(index) {
let s;
switch (index) {
case 1:
s = "square";
break;
case 2:
s = "triangle";
break;
case 3:
s = "triangle-down";
break;
case 4:
s = "diamond";
break;
default: // 0 index
s = "circle";
break;
}
return s;
}
getChartConfig() {
return {
type: "line",
backgroundColor: shared_1.backgroundColor,
style: {
fontFamily: shared_1.chartFontFamily,
},
events: {
// on chart redraw trigger updateMarkers to look for changes in xAxis tick marks and adjust series markers visibility accordingly
redraw: () => this.updateMarkers(),
},
};
}
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 || "{value:%b %e}",
align: "center",
style: {
color: shared_1.labelsColor, // setting label colors
},
},
tickWidth: 0, // hide the vertical tick on xAxis labels
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 maxVal = 0; // use to determine the highest yAxis value
// configure the symbol used for each series markers
for (const seriesItem of series) {
for (const seriesItemData of seriesItem.data) {
maxVal = Math.max(seriesItemData.y, maxVal);
}
}
return {
gridLineColor: shared_1.gridColor, // sets the horizontal grid line colors
opposite: true, // moves yAxis labels to the right side of the chart
labels: {
// if yAxisLabels are not passed in display the standard label
format: this.input.yAxisLabels ? undefined : "${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];
}
: undefined,
style: {
color: shared_1.labelsColor, // setting label colors
},
},
max: maxVal,
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: Array.isArray(this.input.series) &&
this.input.series.length > 1,
symbolRadius: 6, // 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: shared_1.legendColor, // set the color of the text in the legend
},
itemHiddenStyle: {
color: shared_1.legendInactiveColor, // set legend text color when legend item has been clicked and hidden
},
itemHoverStyle: {
color: shared_1.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
return tooltip_marko_1.default.renderToString({
// eslint-disable-next-line no-undef,new-cap
date: Highcharts.dateFormat("%b %e, %Y", this.points[0].x, false),
points: this.points,
seriesLength: Array.isArray(component.input.series) &&
component.input.series.length > 1,
});
},
useHTML: true, // allows defining html to format tooltip content
backgroundColor: shared_1.tooltipBackgroundColor, // sets tooltip background color
borderWidth: 0, // hide the default border stroke
borderRadius: 10, // set the border radius of the tooltip
outside: component.input.renderTooltipOutside !== false, // used to render the tooltip outside of the main SVG element
shadow: false, // hide the default shadow as it conflicts with designs
crosshair: {
dashStyle: "Solid", // makes a yaxis cross hair appear over the hovered xAxis data points
},
shared: true, // shared means that if there are multipe series passed in there will be a single tooltip element per xAxis point
style: {
filter: shared_1.tooltipShadows, // sets tooltip shadows
fontSize: "12px",
},
};
}
getPlotOptionsConfig(series) {
const mouseOut = (0, event_utils_1.debounce)(() => this.handleMouseOut(), 80);
const mouseOver = (0, event_utils_1.debounce)((e) => this.handleMouseOver(e), 85); // 85ms delay for debounce so it doesn't colide with mouseOut debounce calls
return {
line: {
events: {
// assign mouse events to point hovers
mouseOut,
},
},
series: {
description: this.input.description, // set the description that was passed in
lineWidth: 3, // sets the line width for series lines
// sets the starting point of the xAxis to the first data point
// if not set the auto resizing of the xAxis will often leave a gap in data on the left hand side
pointStart: series[0].data[0].x,
point: {
// assign mouse events to point hovers
events: {
mouseOver,
mouseOut,
},
},
},
};
}
handleMouseOut() {
// this function is debounced to improve performance
this.chartRef.series.forEach((s) => {
s.data.forEach((data) => {
// check if hover is on the xAxis (onTick) for each item,
// and if they have a className remove and disable the marker
if (!data.onTick && data.className !== null) {
data.update({
className: undefined, // nullify className if not active
marker: {
enabled: false, // disable marker if not active
},
}, false, // disable auto redraw
false);
}
else if (data.onTick && data.className === null) {
data.update({
className: "ebay-line-graph__marker--visible", // set classname
onTick: data.onTick, // sets the onTick flag to keep track of the points enabled status for mouse events
marker: {
enabled: true, // set marker enabled
radius: pointSize, // set the size of marker
lineColor: shared_1.backgroundColor, // set border color of hover markers
lineWidth: 2, // sets the border line width of the marker symbol
},
}, false, // disable auto redraw
false);
}
});
});
this.chartRef.redraw(); // trigger redraw after all points have been updated
}
handleMouseOver(e) {
// this function is debounced to improve performance
this.chartRef.series.forEach((s) => {
s.data.forEach((data) => {
// if active xAxis hover position matches the data point x update the marker to display
if (data.x === e.target.x) {
data.update({
className: "ebay-line-graph__marker--visible", // sets the classname
onTick: data.onTick, // sets the onTick flag to keep track of the points enabled status for mouse events
marker: {
enabled: true, // set marker enabled
radius: pointSize, // set the size of marker
lineColor: shared_1.backgroundColor, // set border color of hover markers
lineWidth: 2, // sets the border line width of the marker symbol
},
}, false, // disable auto redraw
false);
}
else if (!data.onTick && data.className !== null) {
data.update({
className: undefined, // nullify className if not active
onTick: data.onTick, // sets the onTick flag to keep track of the points enabled status for mouse events
marker: {
enabled: false, // disable marker
},
}, false, // disable auto redraw
false);
}
});
});
this.chartRef.redraw(); // trigger redraw after all points have been updated
}
updateMarkers(e) {
if (this.input.plotPoints) {
// ticks is an object with the xaxis date values as their keys
// setting tickValues to the keys of the ticks object and parsing into an int for data matching of xValues in series below
this.tickValues = Object.keys(this.chartRef.axes[0].ticks).map((value) => parseInt(value, 10));
// this checks if the resize has adjust the number of xAxis tick marks, and if so make updates
if (this.axisTicksLength !== this.tickValues.length || e === true) {
// update the axisTicksLenth variable used for checks in the updateMarkers calls
this.axisTicksLength = this.tickValues.length;
// loops through each series if a className exist remove and hide the marker
this.chartRef.series.forEach((series) => {
// looping through each series data array
series.data.forEach((data) => {
if (data.className !== null) {
data.update({
className: undefined, // removing className used to help keep track of active markers
onTick: false, // sets the onTick flag to keep track of the points enabled status for mouse events
marker: {
enabled: false, // disable the marker
},
}, false, // disable auto redraw
false);
}
});
});
// loop through each series again and update markers that line up to xAxis tick marks
this.chartRef.series.forEach((series) => {
// loop through each searies data objects
series.data.forEach((data) => {
// loop through the tickValues that come from the x axis ticks and are epoch time stamps
this.tickValues.forEach((tick) => {
// if the current point x value matches the tickValue or the updateMarkers event exist from the redraw event
if (tick === data.x || data.x === e) {
if (data.className === null) {
data.update({
className: "ebay-line-graph__marker--visible", // add the ebay-line-graph__marker--visible class to boost it's visibility
onTick: true, // sets the onTick flag to keep track of the points enabled status for mouse events
marker: {
enabled: true, // set marker enabled
radius: pointSize, // set the size of the marker
lineColor: shared_1.backgroundColor, // set the border color of the hover markers
lineWidth: 2, // set the border width of the hover markers
},
}, false, // disable auto redraw
false);
}
}
});
});
});
this.chartRef.redraw(); // trigger redraw after all points have been updated
}
}
}
onDestroy() {
this.chartRef.destroy();
}
}
module.exports = LineChart;