diff --git a/package.json b/package.json index 13d19cc..f53ea69 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adx-query-charts", - "version": "1.1.26", + "version": "1.1.27", "description": "Draw charts from Azure Data Explorer queries", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -38,7 +38,7 @@ }, "dependencies": { "css-element-queries": "^1.2.3", - "highcharts": "^8.0.0", + "highcharts": "^8.1.0", "lodash": "^4.17.15" }, "jest": { diff --git a/src/visualizers/highcharts/charts/chart.ts b/src/visualizers/highcharts/charts/chart.ts index e956672..a490000 100644 --- a/src/visualizers/highcharts/charts/chart.ts +++ b/src/visualizers/highcharts/charts/chart.ts @@ -149,6 +149,12 @@ export abstract class Chart { return categoriesAndSeries; } + public sortSeriesByName(series: any[]): any[] { + const sortedSeries = _.sortBy(series, 'name'); + + return sortedSeries; + } + public getChartTypeOptions(): Highcharts.Options { return { chart: { diff --git a/src/visualizers/highcharts/charts/pie.ts b/src/visualizers/highcharts/charts/pie.ts index a15f538..ecec043 100644 --- a/src/visualizers/highcharts/charts/pie.ts +++ b/src/visualizers/highcharts/charts/pie.ts @@ -111,7 +111,18 @@ export class Pie extends Chart { series: series } } - + + public sortSeriesByName(series: any[]): any[] { + const allData = _.flatMap(series, 'data'); + const sortedData = _.sortBy(allData, 'name'); + + sortedData.forEach((data, i) => { + data.legendIndex = ++i; + }); + + return series; + } + public getChartTooltipFormatter(chartOptions: IChartOptions): Highcharts.TooltipFormatterCallbackFunction { const self = this; diff --git a/src/visualizers/highcharts/highchartsVisualizer.ts b/src/visualizers/highcharts/highchartsVisualizer.ts index 817ce68..aabed2a 100644 --- a/src/visualizers/highcharts/highchartsVisualizer.ts +++ b/src/visualizers/highcharts/highchartsVisualizer.ts @@ -341,7 +341,7 @@ export class HighchartsVisualizer implements IVisualizer { xAxis: { categories: categoriesAndSeries.categories }, - series: categoriesAndSeries.series + series: this.currentChart.sortSeriesByName(categoriesAndSeries.series) } } diff --git a/test/highcharts/chart.test.ts b/test/highcharts/chart.test.ts index 4222f1b..46a7ae4 100644 --- a/test/highcharts/chart.test.ts +++ b/test/highcharts/chart.test.ts @@ -698,5 +698,310 @@ describe('Unit tests for Chart methods', () => { //#endregion Pie chart getSplitByCategoriesAndSeries }); + describe('Validate sortSeriesByName method', () => { + //#region Line chart - no split by + + it('Validate line chart sortSeriesByName when there is no split by', () => { + // Input + const series = [{ + name: 'request_count', + data: [30, 100, 20] + }]; + + // Act + const chart = ChartFactory.create(ChartType.Line); + const sortedSeries: any = chart.sortSeriesByName(series); + const expected = series; + + // Assert + expect(sortedSeries).toEqual(expected); + }); + + it('Validate line chart sortSeriesByName when there is no split by and date x-axis', () => { + // Input + const series = [{ + name: 'request_count', + data: [[2019, 30], [2019, 20], [2000, 100]] + }]; + + // Act + const chart = ChartFactory.create(ChartType.Line); + const sortedSeries: any = chart.sortSeriesByName(series); + const expected = series; + + // Assert + expect(sortedSeries).toEqual(expected); + }); + + it('Validate line chart sortSeriesByName when there are 3 lines', () => { + // Input + const series_1 = { + name: 'AAA', + data: [[2019, 300], [2019, 150], [2000, 200]] + }; + + const series_2 = { + name: 'ABA', + data: [[2019, 30], [2019, 20], [2000, 100]] + }; + + const series_3 = { + name: 'BBB', + data: [[2019, 30], [2019, 20], [2000, 100]] + }; + + const series = [series_2, series_1, series_3]; + + // Act + const chart = ChartFactory.create(ChartType.Line); + const sortedSeries: any = chart.sortSeriesByName(series); + const expected = [series_1, series_2, series_3]; + + // Assert + expect(sortedSeries).toEqual(expected); + }); + + //#endregion Line chart - no split by + + //#region Line chart - split by + + it('Validate sortSeriesByName for Line chart: non-date x-axis with splitBy', () => { + // Input + const series_1 = { name: 'Almaty', data: [20, null, null] }; + const series_2 = { name: 'Atlanta', data: [300, null, null] }; + const series_3 = { name: 'Boston', data: [200, null, null] }; + const series_4 = { name: 'Herzliya', data: [null, 1000, null] }; + const series_5 = { name: 'Jerusalem', data: [null, 5, null] }; + const series_6 = { name: 'New York', data: [100, null, null] }; + const series_7 = { name: 'Tel Aviv', data: [null, 10, null] }; + const series_8 = { name: 'Tokyo', data: [null, null, 20] }; + const series = [series_6, series_7, series_2, series_1, series_8, series_3, series_5, series_4]; + + // Act + const chart = ChartFactory.create(ChartType.Line); + const sortedSeries: any = chart.sortSeriesByName(series); + const expected = [series_1, series_2, series_3, series_4, series_5, series_6, series_7, series_8]; + + // Assert + expect(sortedSeries).toEqual(expected); + }); + + it('Validate sortSeriesByName for Line chart: date x-axis with splitBy', () => { + // Input + const series_1 = { name: 'Almaty', data: [[2019, 20]]}; + const series_2 = { name: 'Atlanta', data: [[2019, 300]] }; + const series_3 = { name: 'Boston', data: [[2000, 200]] }; + const series_4 = { name: 'Herzliya', data: [[2000, 1000]] }; + const series_5 = { name: 'Jerusalem', data: [[1988, 500]] }; + const series_6 = { name: 'New York', data: [[2000, 100]] }; + const series_7 = { name: 'Tel Aviv', data: [[2000, 10]] }; + const series_8 = { name: 'Tokyo', data: [[2019, 20]] }; + const series = [series_8, series_6, series_2, series_1, series_4, series_7, series_3, series_5]; + + // Act + const chart = ChartFactory.create(ChartType.Line); + const sortedSeries: any = chart.sortSeriesByName(series); + const expected = [series_1, series_2, series_3, series_4, series_5, series_6, series_7, series_8]; + + // Assert + expect(sortedSeries).toEqual(expected); + }); + + //#endregion Line chart - split by + + //#region Pie chart + + it('Validate pie chart sortSeriesByName when there is no split by', () => { + // Input + const series = [{ + name: 'request_count', + data: [ + { name: 'Herzliya', y: 30 }, + { name: 'Boston', y: 1 }, + { name: 'Tel Aviv', y: 10 }, + { name: 'Miami', y: 3 }, + { name: 'Coal Creek', y: 5 }, + { name: 'New York', y: 2 }, + { name: 'Jaffa', y: 50 }, + ] + }]; + + // Act + const chart = ChartFactory.create(ChartType.Pie); + const sortedSeries: any = chart.sortSeriesByName(series); + const expected = [{ + name: 'request_count', + data: [ + { name: 'Herzliya', y: 30, legendIndex: 3 }, + { name: 'Boston', y: 1, legendIndex: 1 }, + { name: 'Tel Aviv', y: 10, legendIndex: 7 }, + { name: 'Miami', y: 3, legendIndex: 5 }, + { name: 'Coal Creek', y: 5, legendIndex: 2 }, + { name: 'New York', y: 2, legendIndex: 6 }, + { name: 'Jaffa', y: 50, legendIndex: 4 }, + ] + }]; + + // Assert + expect(sortedSeries).toEqual(expected); + }); + + it('Validate pie chart sortSeriesByName when there are 2 pie levels', () => { + // Input + const series = [{ + name: 'country', + size: '50%', + data: [ + { name: 'Israel', y: 90 }, + { name: 'United States', y: 11} + ] + }, + { + name: 'city', + size: '60%', + innerSize: '50%', + data: [ + { name: 'Tel Aviv', y: 10 }, + { name: 'Herzliya', y: 30 }, + { name: 'Jaffa', y: 50 }, + { name: 'Beer sheva', y: 5 }, + { name: 'Iceland', y: 2 }, + { name: 'Miami', y: 3 }, + { name: 'Boston', y: 1 }, ] + }]; + + // Act + const chart = ChartFactory.create(ChartType.Donut); + const sortedSeries: any = chart.sortSeriesByName(series); + const expected = [{ + name: 'country', + size: '50%', + data: [ + { name: 'Israel', y: 90, legendIndex: 5 }, + { name: 'United States', y: 11, legendIndex: 9 } + ] + }, + { + name: 'city', + size: '60%', + innerSize: '50%', + data: [ + { name: 'Tel Aviv', y: 10, legendIndex: 8 }, + { name: 'Herzliya', y: 30, legendIndex: 3 }, + { name: 'Jaffa', y: 50, legendIndex: 6 }, + { name: 'Beer sheva', y: 5, legendIndex: 1 }, + { name: 'Iceland', y: 2, legendIndex: 4 }, + { name: 'Miami', y: 3, legendIndex: 7 }, + { name: 'Boston', y: 1, legendIndex: 2 }, ] + }]; + + // Assert + expect(sortedSeries).toEqual(expected); + }); + + it('Validate pie chart sortSeriesByName when there are 3 pie levels', () => { + // Input + const series = [{ + name: 'browser', + size: '33%', + data: [ + { name: 'Internet Explorer', y: 20 }, + { name: 'Chrome', y: 50 }, + { name: 'Firefox', y: 10 }, + { name: 'Safari', y: 20 } + ] + }, + { + name: 'version', + size: '43%', + innerSize: '33%', + data: [ + { name: 'v8', y: 19 }, + { name: 'v11', y: 1 }, + { name: 'v65', y: 25 }, + { name: 'v66', y: 25 }, + { name: 'v58', y: 7 }, + { name: 'v59', y: 3 }, + { name: 'v11', y: 20 } + ] + }, + { + name: 'minor_version', + size: '53%', + innerSize: '43%', + data: [ + { name: '0', y: 10 }, + { name: '1', y: 1 }, + { name: '2', y: 5 }, + { name: '3', y: 3 }, + { name: '0', y: 1 }, + { name: '0', y: 5 }, + { name: '1', y: 20 }, + { name: '0', y: 15 }, + { name: '1', y: 5 }, + { name: '2', y: 5 }, + { name: '0', y: 5 }, + { name: '1', y: 2 }, + { name: '0', y: 3 }, + { name: '0', y: 20 } + ] + }]; + + // Act + const chart = ChartFactory.create(ChartType.Donut); + const sortedSeries: any = chart.sortSeriesByName(series); + const expected = [{ + name: 'browser', + size: '33%', + data: [ + { name: 'Internet Explorer', y: 20, legendIndex: 17 }, + { name: 'Chrome', y: 50, legendIndex: 15 }, + { name: 'Firefox', y: 10, legendIndex: 16 }, + { name: 'Safari', y: 20, legendIndex: 18 } + ] + }, + { + name: 'version', + size: '43%', + innerSize: '33%', + data: [ + { name: 'v8', y: 19, legendIndex: 25 }, + { name: 'v11', y: 1, legendIndex: 19 }, + { name: 'v65', y: 25, legendIndex: 23 }, + { name: 'v66', y: 25, legendIndex: 24 }, + { name: 'v58', y: 7, legendIndex: 21 }, + { name: 'v59', y: 3, legendIndex: 22 }, + { name: 'v11', y: 20, legendIndex: 20 } + ] + }, + { + name: 'minor_version', + size: '53%', + innerSize: '43%', + data: [ + { name: '0', y: 10, legendIndex: 1 }, + { name: '1', y: 1, legendIndex: 8 }, + { name: '2', y: 5, legendIndex: 12 }, + { name: '3', y: 3, legendIndex: 14 }, + { name: '0', y: 1, legendIndex: 2 }, + { name: '0', y: 5, legendIndex: 3 }, + { name: '1', y: 20, legendIndex: 9 }, + { name: '0', y: 15, legendIndex: 4 }, + { name: '1', y: 5, legendIndex: 10 }, + { name: '2', y: 5, legendIndex: 13 }, + { name: '0', y: 5, legendIndex: 5 }, + { name: '1', y: 2, legendIndex: 11 }, + { name: '0', y: 3, legendIndex: 6 }, + { name: '0', y: 20, legendIndex: 7 } + ] + }]; + + // Assert + expect(sortedSeries).toEqual(expected); + }); + + //#endregion Pie chart + }); + //#endregion Tests }); \ No newline at end of file