UNPKG

@ebay/ebayui-core

Version:

Collection of core eBay components; considered to be the building blocks for all composite structures, pages & apps.

335 lines (334 loc) 15.2 kB
"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 legend_1 = require("../../common/charts/legend"); const bar_chart_1 = require("../../common/charts/bar-chart"); const subtemplate_marko_1 = __importDefault(require("./subtemplate.marko")); class BarChart { 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(); } } onMount() { (0, highcharts_1.load)() .then(() => { this.handleSuccess(); }) .catch((e) => { this.handleError(e); }); } handleError(err) { this.emit("load-error", err); } handleSuccess() { this._initializeHighchartsExtensions(); this._setupCharts(); } getContainerId() { return `ebay-bar-chart-${this.id}`; } _initializeHighchartsExtensions() { // add custom legend wrapper function // eslint-disable-next-line no-undef,new-cap (0, legend_1.ebayLegend)(Highcharts); // add custom columns wrapper to enable rounded bar corners, and stacks with spaces between each stacked point // eslint-disable-next-line no-undef,new-cap (0, bar_chart_1.eBayColumns)(Highcharts); } _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]; const stacked = this.input.stacked; const title = this.input.title; // controls rounded corders and spacing at the bottom of data points if (stacked) { series[0].bottom = true; // set a variable on the first series so it renders rounder corners on the bottom of the bar series[series.length - 1].top = true; // set a variable on the last series to render rounded corner on the top of the bar series.forEach((s) => { // used to help link each series to the previous one for stacked views // refer to https://api.highcharts.com/highcharts/series.column.linkedTo s.group = ":previous"; }); } else { // if not stacked, set the top and bottom flag on each series so the single bar has rounded top and bottom corners series.forEach((s) => { s.top = true; s.bottom = true; }); } // Cast series to Highcharts.SeriesBarOptions[] to avoid type errors (0, shared_1.setSeriesColors)(series); const config = { title: { text: title, // set the title that will render above the chart }, chart: this.getChartConfig(), colors: shared_1.colorMapping, xAxis: this.getXAxisConfig(), yAxis: this.getYAxisConfig(series), legend: this.getLegendConfig(), tooltip: this.getTooltipConfig(), plotOptions: this.getPlotOptionsConfig(), series: series, credits: { enabled: false, // hide the highcharts label and link in the bottom right }, }; // eslint-disable-next-line no-undef,new-cap this.chartRef = Highcharts.chart(this.getContainerId(), config); this.chartRef.redraw(); } getChartConfig() { return { type: "column", backgroundColor: shared_1.backgroundColor, style: { fontFamily: shared_1.chartFontFamily, // set the font for all chart svg text elements }, }; } getXAxisConfig() { const xAxisLabelFormat = this.input.xAxisLabelFormat; const xAxisPositioner = this.input.xAxisPositioner; 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: xAxisLabelFormat || "{value:%b %e}", align: "center", style: { color: shared_1.labelsColor, // setting label colors }, }, tickWidth: 0, // hide the vertical tick on xAxis labels tickPositioner: xAxisPositioner, // optional input to allow configuring the position of xAxis tick marks }; } getYAxisConfig(series) { const yAxisLabels = this.input.yAxisLabels; const yAxisPositioner = this.input.yAxisPositioner; let maxVal = 0; // use to determine the highest yAxis value series.forEach((s) => { maxVal = s.data.reduce((p, c) => (c > p ? c : p), maxVal); }); let yLabelsItterator = 0; return { gridLineColor: shared_1.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: { format: 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: yAxisLabels ? function () { if (this.isFirst) { yLabelsItterator = -1; } yLabelsItterator = yLabelsItterator + 1; return 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: yAxisPositioner, }; } getLegendConfig() { const series = this.input.series; return { symbolRadius: 2, // set corner radius of legend identifiers enabled: Array.isArray(series) && series.length > 1, // disabled legend if only one series is passed in itemStyle: { color: shared_1.legendColor, // set the color of the legend text }, itemHiddenStyle: { color: shared_1.legendInactiveColor, // set the color of the legend text when clicked to hide }, itemHoverStyle: { color: shared_1.legendHoverColor, // set the color of the legend text when hovering }, }; } // returns a function that can be called on each mouseover event tooltipFormatter() { const stacked = this.input.stacked; return function () { // references to the charts updates series array, only available when the returned tooltip function is called and not before const series = this.series.chart.series; // refer to https://api.highcharts.com/class-reference/Highcharts.Time#dateFormat for dateFormat variables return subtemplate_marko_1.default.renderToString({ // eslint-disable-next-line no-undef,new-cap date: Highcharts.dateFormat("%b %e, %Y", this.x, false), data: stacked ? series : this.point, stacked, x: this.x, }); }; } tooltipPositioner(labelWidth, labelHeight) { const series = this.chart.series; const chartPosition = this.chart.pointer.getChartPosition(); // returns the pointers top and left positions const hpIndex = this.chart.hoverPoint.index; // reference to the index of the original hovered point of the series const hoverPoint = this.chart.hoverPoint, // reference to the original hovered point of the series y = // setting the y position of the tooltip to the top of the hovered stack of points chartPosition.top + (hoverPoint === null || hoverPoint === void 0 ? void 0 : hoverPoint.series.yAxis).top + series[series.length - 1].data[hpIndex].shapeY - labelHeight - 15; // adjust for the arrow let x = // setting the x position of the tooltip based on the center of the hovered stack of points chartPosition.left + hoverPoint.dlBox.x + hoverPoint.dlBox.width * 0.5 - labelWidth * 0.5 + 3; // offset padding // check left bound and adjust if the tooltip would be clipped if (x < 6) { x = 6; } // check right bound and adjust if the tooltip would be clipped if (x + hoverPoint.dlBox.width > chartPosition.left + this.chart.chartWidth - 6) { x = chartPosition.left + this.chart.chartWidth - hoverPoint.dlBox.width - 6; } return { x, y }; // return the tooltip x and y position } getTooltipConfig() { const stacked = this.input.stacked; return { formatter: this.tooltipFormatter(), 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: true, // used to render the tooltip outside of the main SVG element shadow: false, // hide the default shadow as it conflicts with designs style: { filter: shared_1.tooltipShadows, // sets tooltip shadows fontSize: "12px", }, // this callback function is used to position the tooltip at the top of the stacked bars positioner: stacked ? this.tooltipPositioner : undefined, }; } legendItemClick() { // returns a function so that can access input values const stacked = this.input.stacked; return function () { const series = this.chart.series; if (stacked) { // setTimeout with 0 ms to push this function to the end of the execution stack to prevent issues with hover events setTimeout(() => { let topFound = false; let bottomFound = false; // loop through and reset bottom variables on series based on their visibility for (let i = 0; i < series.length; i++) { if (!bottomFound && series[i].visible) { series[i].options.bottom = true; bottomFound = true; } else { series[i].options.bottom = false; } } // loop through and reset top variables on series based on their visibility for (let i = series.length - 1; i >= 0; i--) { if (!topFound && series[i].visible) { series[i].options.top = true; topFound = true; } else { series[i].options.top = false; } } this.chart.redraw(); // redraw the chart after all series variables have been updated }, 0); } }; } // handleMouseOver returns a function while keeping scope to the class compnent to access input values handleMouseOver() { const stacked = this.input.stacked; return function () { const refPoint = this; // this is the active hovered point of the series const chart = this.series.chart; chart.series.forEach((s) => s.points.forEach((p) => { // loop through each point in the series if ((stacked && p.x !== refPoint.x) || // if the stacked flag is set to true and each points x value does not match (!stacked && p !== refPoint) // or if not stacked and refPoint does not match the current point ) { p.update({ opacity: 0.2, // set opacity }, false); } })); chart.redraw(); // trigger chart redraw after all points have been updated }; } handleMouseOut() { const chart = this.series.chart; chart.series.forEach((s) => s.points.forEach((p) => p.update({ opacity: 1, // update the opacity to 1 }, false))); chart.redraw(); // trigger chart redraw after all points have been updated } getPlotOptionsConfig() { const description = this.input.description; const stacked = this.input.stacked; return { series: { description, }, column: { events: { legendItemClick: this.legendItemClick(), }, stacking: stacked ? "normal" : undefined, // set stacking to normal if stacked flag is set groupPadding: 0.1, // padding around groups of points pointPadding: 0.15, // padding between single points states: { inactive: { opacity: 1, // prevents other points in the same stack from fading out }, }, point: { events: { mouseOver: this.handleMouseOver(), // handleMouseOver returns a function so it can access component input values mouseOut: this.handleMouseOut, }, }, }, }; } onDestroy() { this.chartRef.destroy(); } } module.exports = BarChart;