diff --git a/README.md b/README.md index 1a1b56a..812b4bc 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ npm install adx-query-charts 1. [lodash](https://www.npmjs.com/package/lodash): `npm i lodash` 2. [css-element-queries](https://www.npmjs.com/package/css-element-queries): `npm i css-element-queries` 3. [highcharts](https://www.npmjs.com/package/highcharts): `npm i highcharts` -Please note: Highcharts/Highstock libraries are free to use with Log Analytics/adx-query-charts.
If you plan to use Highcharts separately, in your own project, you must obtain a license:
follow the link − [License and Pricing](https://shop.highsoft.com/highcharts). +Please note: Highcharts/Highstock libraries are free to use with Log Analytics/adx-query-charts.
If you plan to use Highcharts separately, in your own project, you must obtain a license:
follow the link − [License and Pricing](https://shop.highsoft.com/highcharts). ## Usage ```typescript @@ -34,42 +34,42 @@ chartHelper.draw(queryResultData, chartOptions); ### KustoChartHelper | Method: | Description: | Input: | Return value: | | ------------------------ |-------------------------- | ----------------------------------------------------------------------------- | ---------------- | -| draw | Draw the chart | [IQueryResultData](#IQueryResultData) - The original query result data
[IChartOptions](#IChartOptions) - The information required to draw the chart | Promise<[IChartInfo](#IChartInfo)> | +| draw | Draw the chart | [IQueryResultData](#IQueryResultData) - The original query result data
[IChartOptions](#IChartOptions) - The information required to draw the chart | Promise<[IChartInfo](#IChartInfo)> | | changeTheme | Change the theme of an existing chart | [ChartTheme](#ChartTheme) - The theme to apply | Promise<void> | -| getSupportedColumnTypes | Get the supported column types for the axes and the split-by
for a specific chart type | [ChartType](#ChartType) - The type of the chart | [ISupportedColumnTypes](#ISupportedColumnTypes) | -| getSupportedColumnsInResult | Get the supported columns from the query result data for the axes and the split-by for a specific chart type | [IQueryResultData](#IQueryResultData) - The original query result data
[ChartType](#ChartType) - The type of the chart | [ISupportedColumns](#ISupportedColumns) | -| getDefaultSelection | Get the default columns selection from the query result data.
Select the default columns for the axes and the split-by for drawing a default chart of a specific chart type. | [IQueryResultData](#IQueryResultData) - The original query result data
[ChartType](#ChartType) - The type of the chart
[ISupportedColumns](#ISupportedColumns) - (Optional) The list of the supported column types for the axes and the split-by | [ColumnsSelection](#ColumnsSelection) | +| getSupportedColumnTypes | Get the supported column types for the axes and the split-by
for a specific chart type | [ChartType](#ChartType) - The type of the chart | [ISupportedColumnTypes](#ISupportedColumnTypes) | +| getSupportedColumnsInResult | Get the supported columns from the query result data for the axes and the split-by for a specific chart type | [IQueryResultData](#IQueryResultData) - The original query result data
[ChartType](#ChartType) - The type of the chart | [ISupportedColumns](#ISupportedColumns) | +| getDefaultSelection | Get the default columns selection from the query result data.
Select the default columns for the axes and the split-by for drawing a default chart of a specific chart type. | [IQueryResultData](#IQueryResultData) - The original query result data
[ChartType](#ChartType) - The type of the chart
[ISupportedColumns](#ISupportedColumns) - (Optional) The list of the supported column types for the axes and the split-by | [ColumnsSelection](#ColumnsSelection) | | downloadChartJPGImage | Download the chart as JPG image | (error: Error) => void - [Optional] A callback that will be called if the module failed to export the chart image | void | ### IChartOptions | Option name: | Type: | Details: | Default value: | | ------------------- |-------------------- | --------------------------------------------- | ----------------| -| chartType | [ChartType](#ChartType) | Mandatory.
The type of the chart to draw | | -| columnsSelection | [ColumnsSelection](#ColumnsSelection)| The columns selection for the Axes and the split-by of the chart | If not provided, default columns will be selected.
See: getDefaultSelection method| -| maxUniqueXValues | number | The maximum number of the unique X-axis values.
The chart will show the biggest values, and the rest will be aggregated to a separate data point.| 100 | +| chartType | [ChartType](#ChartType) | Mandatory.
The type of the chart to draw | | +| columnsSelection | [ColumnsSelection](#ColumnsSelection)| The columns selection for the Axes and the split-by of the chart | If not provided, default columns will be selected.
See: getDefaultSelection method| +| maxUniqueXValues | number | The maximum number of the unique X-axis values.
The chart will show the biggest values, and the rest will be aggregated to a separate data point.| 100 | | exceedMaxDataPointLabel| string | The label of the data point that contains the aggregated value of all the X-axis values that exceed the 'maxUniqueXValues'| 'OTHER' | -| aggregationType | [AggregationType](#AggregationType) | Multiple rows with the same values for the X-axis and the split-by will be aggregated using a function of this type.
For example, assume we get the following query result data:
['2016-08-02T10:00:00Z', 'Chrome 51.0', 15],
['2016-08-02T10:00:00Z', 'Internet Explorer 9.0', 4]
When drawing a chart with columnsSelection = { xAxis: timestamp, yAxes: count_ }, and aggregationType = AggregationType.Sum we need to aggregate the values of the same timestamp value and return one row with ["2016-08-02T10:00:00Z", 19] | AggregationType.Sum | +| aggregationType | [AggregationType](#AggregationType) | Multiple rows with the same values for the X-axis and the split-by will be aggregated using a function of this type.
For example, assume we get the following query result data:
['2016-08-02T10:00:00Z', 'Chrome 51.0', 15],
['2016-08-02T10:00:00Z', 'Internet Explorer 9.0', 4]
When drawing a chart with columnsSelection = { xAxis: timestamp, yAxes: count_ }, and aggregationType = AggregationType.Sum we need to aggregate the values of the same timestamp value and return one row with ["2016-08-02T10:00:00Z", 19] | AggregationType.Sum | | title | string | The title of the chart | | | legendOptions | [ILegendOptions](#ILegendOptions) | The legend configuration options | | | yMinimumValue | number | The minimum value to be displayed on the y-axis
If not provided, the minimum value is automatically calculated | | | yMaximumValue | number | The maximum value to be displayed on the y-axis
If not provided, the maximum value is automatically calculated | | | fontFamily | string | Chart labels font family | 'az_ea_font, wf_segoe-ui_normal, \"Segoe UI\", \"Segoe WP\", Tahoma, Arial, sans-serif' | | chartTheme | [ChartTheme](#ChartTheme) | The theme of the chart | ChartTheme.Light | -| getUtcOffset | Function
(dateStr: string): number | Callback that is used to get the desired offset from UTC in hours for date value. Used to handle timezone.
The offset will be added to the original date from the query results data.
For example:
For 'South Africa Standard Time' timezone provide utcOffset = 2 and the displayed date will be:
'11/25/2019, 04:00 PM' instead of '11/25/2019, 02:00 PM'
See time zone [info](https://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11)
Callback inputs:
    dateStr - The original date string from the query result. For example: '2019-11-25T07:14:00.000Z'
Callback return value:
    The desired offset from UTC in hours | If not provided, the utcOffset will be 0 | -| dateFormatter | Function
(dateValue: Date, defaultFormat: DateFormat): string| Callback that is used to format the date values both in the axis and the tooltip
Callback inputs:
    dateValue - The original date value. If utcOffset was provided, this value will include the utcOffset
    [DateFormat](#DateFormat) - The default format of the label
Callback return value:
    The string represents the display value of the dateValue| If not provided - the default formatting will apply | -| numberFormatter | Function
(numberValue: number): string | Callback that is used to format number values both in the axis and the tooltip
Callback inputs:
    numberValue - The original number
Callback return value:
    The string represents the display value of the numberValue |If not provided - the default formatting will apply | -| xAxisTitleFormatter | Function
(xAxisColumn: IColumn): string | Callback that is used to get the xAxis title
Callback inputs:
    [IColumn](#IColumn) - The x-axis column
Callback return value:
    The desired x-axis title | If not provided - the xAxis title will be the xAxis column name | -| yAxisTitleFormatter | Function
(yAxisColumns: IColumn[]): string | Callback that is used to get the yAxis title
Callback inputs:
    [IColumn[]](#IColumn) - The y-axis columns
Callback return value:
    The desired y-axis title | If not provided - the yAxis title will be the first yAxis column name | -| onFinishDataTransformation | Function(dataTransformationInfo: IDataTransformationInfo) : Promise<boolean> | Callback that is called when all the data transformations required to draw the chart are finished
Callback inputs:
    [IDataTransformationInfo](#IDataTransformationInfo) - The information regarding the applied transformations on the original query results
Callback return value:
    The promise that is used to continue/stop drawing the chart
    When provided, the drawing of the chart will be suspended until this promise will be resolved
    When resolved with true - the chart will continue the drawing
    When resolved with false - the chart drawing will be canceled | | -| onFinishDrawing | Function(chartInfo: IChartInfo) : void | Callback that is called when the chart drawing is finished
Callback inputs:
    [IChartInfo](#IChartInfo) - The information regarding the chart | | | -| onFinishChartAnimation | Function(chartInfo: IChartInfo) : void | Callback that is called when the chart animation is finished
Callback inputs:
    [IChartInfo](#IChartInfo) - The information regarding the chart | | | +| getUtcOffset | Function
(dateValue: number): number | Callback that is used to get the desired offset from UTC in minutes for date value. Used to handle timezone.
The offset will be added to the original UTC date from the query results data.
For example:
For 'South Africa Standard Time' timezone return -120 and the displayed date will be:
'11/25/2019, 02:00 PM' instead of '11/25/2019, 04:00 PM'
See time zone [info](https://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11)
If dateFormatter wasn't provided, the callback will be also used for the X axis labels and the tooltip header. Otherwise - it will only be used for positionning the x-axis.
Callback inputs:
    dateValue - The time value in milliseconds since midnight, January 1, 1970 UTC of the date from the query result.    
For example: 1574666160000 represent '2019-11-25T07:16:00.000Z'
Callback return value:
    The desired offset from UTC in hours | If not provided, the utcOffset will be 0 | +| dateFormatter | Function
(dateValue: number, defaultFormat: DateFormat): string | Callback that is used to format the date values both in the axis and the tooltip
Callback inputs:
    dateValue - The original date value in milliseconds since midnight, January 1, 1970 UTC
    [DateFormat](#DateFormat) - The default format of the label
Callback return value:
    The string represents the display value of the dateValue| If not provided - the default formatting will apply | +| numberFormatter | Function
(numberValue: number): string | Callback that is used to format number values both in the axis and the tooltip
Callback inputs:
    numberValue - The original number
Callback return value:
    The string represents the display value of the numberValue |If not provided - the default formatting will apply | +| xAxisTitleFormatter | Function
(xAxisColumn: IColumn): string | Callback that is used to get the xAxis title
Callback inputs:
    [IColumn](#IColumn) - The x-axis column
Callback return value:
    The desired x-axis title | If not provided - the xAxis title will be the xAxis column name | +| yAxisTitleFormatter | Function
(yAxisColumns: IColumn[]): string | Callback that is used to get the yAxis title
Callback inputs:
    [IColumn[]](#IColumn) - The y-axis columns
Callback return value:
    The desired y-axis title | If not provided - the yAxis title will be the first yAxis column name | +| onFinishDataTransformation | Function(dataTransformationInfo: IDataTransformationInfo) : Promise<boolean> | Callback that is called when all the data transformations required to draw the chart are finished
Callback inputs:
    [IDataTransformationInfo](#IDataTransformationInfo) - The information regarding the applied transformations on the original query results
Callback return value:
    The promise that is used to continue/stop drawing the chart
    When provided, the drawing of the chart will be suspended until this promise will be resolved
    When resolved with true - the chart will continue the drawing
    When resolved with false - the chart drawing will be canceled | | +| onFinishDrawing | Function(chartInfo: IChartInfo) : void | Callback that is called when the chart drawing is finished
Callback inputs:
    [IChartInfo](#IChartInfo) - The information regarding the chart | | | +| onFinishChartAnimation | Function(chartInfo: IChartInfo) : void | Callback that is called when the chart animation is finished
Callback inputs:
    [IChartInfo](#IChartInfo) - The information regarding the chart | | | ### IDataTransformationInfo | Option name: | Type: | Details: | | -------------------------- |------------------------------------- | -------------------------------------------------------------------------------------------------- | | numberOfDataPoints | number | The amount of the data points that will be drawn for the chart | -| isPartialData | boolean | True if the chart presents partial data from the original query results
The chart data will be partial when the maximum number of the unique X-axis values exceed the
'maxUniqueXValues' in [IChartOptions](#IChartOptions) | -| isAggregationApplied | boolean | True if aggregation was applied on the original query results in order to draw the chart
See 'aggregationType' in [IChartOptions](#IChartOptions) for more details | +| isPartialData | boolean | True if the chart presents partial data from the original query results
The chart data will be partial when the maximum number of the unique X-axis values exceed the
'maxUniqueXValues' in [IChartOptions](#IChartOptions) | +| isAggregationApplied | boolean | True if aggregation was applied on the original query results in order to draw the chart
See 'aggregationType' in [IChartOptions](#IChartOptions) for more details | ### IChartInfo | Option name: | Type: | Details: | diff --git a/src/common/chartModels.ts b/src/common/chartModels.ts index 5009c49..2bb29b3 100644 --- a/src/common/chartModels.ts +++ b/src/common/chartModels.ts @@ -217,25 +217,26 @@ export interface IChartOptions { fontFamily?: string; /** - * Callback that is used to get the desired offset from UTC in hours for date value. Used to handle timezone. - * The offset will be added to the original date from the query results data. - * The callback input is the string value of the date from the query result. For example: '2019-11-25T07:14:00.000Z' - * For example: - * For 'South Africa Standard Time' timezone return 2 and the displayed date will be '11/25/2019, 04:00 PM' instead of '11/25/2019, 02:00 PM' + * Callback that is used to get the desired offset from UTC in minutes for date value. Used to handle timezone. + * The offset will be added to the original UTC date from the query results data. + * The callback input is the time value in milliseconds since midnight, January 1, 1970 UTC of the date from the query result. + * For example: 1574666160000 represent '2019-11-25T07:16:00.000Z' + * For 'South Africa Standard Time' timezone return -120 and the displayed date will be '11/25/2019, 02:00 PM' instead of '11/25/2019, 04:00 PM' * See time zone info: https://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx + * If dateFormatter wasn't provided, the callback will be also used for the X axis labels and the tooltip header. Otherwise - it will only be used for positionning the x-axis. * [Default value: 0] */ - getUtcOffset?: (dateStr: string) => number; + getUtcOffset?: (dateValue: number) => number; /** - * Callback that is used to format the date values both in the axis and the tooltip. If not provided - the default formatting will apply + * Callback that is used to format the date values both in the axis and the tooltip. If not provided - the default formatting will apply. * Callback inputs: - * @param dateValue - The original date value. If utcOffset was provided, this value will include the utcOffset. + * @param dateValue - The original date value. * @param defaultFormat - The default format of the label. * Callback return value: * @returns The string represents the display value of the dateValue */ - dateFormatter?: (dateValue: Date, defaultFormat: DateFormat) => string; + dateFormatter?: (dateValue: number, defaultFormat: DateFormat) => string; /** * Callback that is used to format number values both in the axis and the tooltip. If isn't provided - the default formatting will apply. diff --git a/src/common/utilities.ts b/src/common/utilities.ts index ef4ede1..232d482 100644 --- a/src/common/utilities.ts +++ b/src/common/utilities.ts @@ -23,24 +23,18 @@ export class Utilities { } /** - * Returns the value of the local date after adding the desired offset (from UTC) - * @param dateStr - The string value that represents the date to transform. - * @param getUtcOffset - Callback that returns the offset in hours from UTC. - * @returns The value of the date + the desired UTC offset + * Returns the stored time value in milliseconds since midnight, January 1, 1970 UTC + * @param dateStr - The string value that represents the date + * @returns The date value in milliseconds since midnight, January 1, 1970 UTC */ - public static getDateValue(dateStr: string, getUtcOffset: (dateStr: string) => number): number { + public static getDateValue(dateStr: string): number { const date = new Date(dateStr); if (date.toDateString() === 'Invalid Date') { return null; - } - - // Add UTC offset to the date - const utcOffset = getUtcOffset(dateStr); - const utcOffsetInMilliseconds = utcOffset * 60 * 60 * 1000; - const localDateValue = date.valueOf(); - - return localDateValue + utcOffsetInMilliseconds; + } + + return date.valueOf() } public static isValidDate(str: string): boolean { diff --git a/src/visualizers/highcharts/charts/chart.ts b/src/visualizers/highcharts/charts/chart.ts index 1f73cb3..54f5e10 100644 --- a/src/visualizers/highcharts/charts/chart.ts +++ b/src/visualizers/highcharts/charts/chart.ts @@ -50,7 +50,7 @@ export abstract class Chart { // If the x-axis is a date, convert its value to milliseconds as this is what expected by 'Highcharts' if(isDatetimeAxis) { - xAxisValue = Utilities.getDateValue(xAxisValue, chartOptions.getUtcOffset); + xAxisValue = Utilities.getDateValue(xAxisValue); if(!xAxisValue) { throw new InvalidInputError(`The x-axis value '${row[xAxisColumnIndex]}' is an invalid date`, ErrorCode.InvalidDate); @@ -227,7 +227,7 @@ export abstract class Chart { let xValue = row[xAxisColumnIndex]; // For date the a-axis, convert its value to ms as this is what expected by Highcharts - xValue = Utilities.getDateValue(xValue, options.chartOptions.getUtcOffset); + xValue = Utilities.getDateValue(xValue); if(!xValue) { throw new InvalidInputError(`The x-axis value '${row[xAxisColumnIndex]}' is an invalid date`, ErrorCode.InvalidDate); diff --git a/src/visualizers/highcharts/common/tooltipHelper.ts b/src/visualizers/highcharts/common/tooltipHelper.ts index 11f09b0..71a4d1e 100644 --- a/src/visualizers/highcharts/common/tooltipHelper.ts +++ b/src/visualizers/highcharts/common/tooltipHelper.ts @@ -2,7 +2,6 @@ import { DraftColumnType, DateFormat, IColumn, IChartOptions } from "../../../common/chartModels"; import { Utilities } from "../../../common/utilities"; -import { HC_Utilities } from "./utilities"; export class TooltipHelper { public static getSingleTooltip(chartOptions: IChartOptions, context: Highcharts.TooltipFormatterContextObject, column: IColumn, originalValue: any, columnName?: string, valueSuffix: string = ''): string { @@ -19,9 +18,7 @@ export class TooltipHelper { if(chartOptions.numberFormatter && Utilities.isNumeric(columnType)) { return chartOptions.numberFormatter(originalValue); } else if(Utilities.isDate(columnType)) { - const utcWithOffsetDate = HC_Utilities.getUtcWithOffsetDate(originalValue); - - return chartOptions.dateFormatter ? chartOptions.dateFormatter(utcWithOffsetDate, DateFormat.FullDate) : utcWithOffsetDate.toString(); + return chartOptions.dateFormatter ? chartOptions.dateFormatter(originalValue, DateFormat.FullDate) : new Date(originalValue).toString(); } return originalValue.toString(); diff --git a/src/visualizers/highcharts/common/utilities.ts b/src/visualizers/highcharts/common/utilities.ts index 4039145..975c4cc 100644 --- a/src/visualizers/highcharts/common/utilities.ts +++ b/src/visualizers/highcharts/common/utilities.ts @@ -20,12 +20,4 @@ export class HC_Utilities { return originalValue; } - - public static getUtcWithOffsetDate(originalValue: number): Date { - const localWithOffsetDate = new Date(originalValue); // HC gets the local date + utc offset addition - const utcWithOffsetDateValue = localWithOffsetDate.valueOf() + (localWithOffsetDate.getTimezoneOffset() * 60 * 1000); // Add the local offset. This way the utc offset addition is added to the UTC, and not to the local - const utcWithOffsetDate = new Date(utcWithOffsetDateValue); - - return utcWithOffsetDate; - } } \ No newline at end of file diff --git a/src/visualizers/highcharts/highchartsVisualizer.ts b/src/visualizers/highcharts/highchartsVisualizer.ts index 5359013..bab2989 100644 --- a/src/visualizers/highcharts/highchartsVisualizer.ts +++ b/src/visualizers/highcharts/highchartsVisualizer.ts @@ -14,7 +14,6 @@ import { IVisualizerOptions } from '../IVisualizerOptions'; import { ChartFactory } from './charts/chartFactory'; import { ChartTheme, DateFormat, IChartOptions, IColumn, DrawChartStatus } from '../../common/chartModels'; import { Changes, ChartChange } from '../../common/chartChange'; -import { HC_Utilities } from './common/utilities'; import { Utilities } from '../../common/utilities'; import { Themes } from './themes/themes'; import { HighchartsDateFormatToCommon } from './highchartsDateFormatToCommon'; @@ -204,6 +203,9 @@ export class HighchartsVisualizer implements IVisualizer { fontFamily: options.chartOptions.fontFamily } }, + time: { + getTimezoneOffset: this.options.chartOptions.getUtcOffset + }, title: { text: chartOptions.title }, @@ -252,9 +254,8 @@ export class HighchartsVisualizer implements IVisualizer { formatter = function() { const dataPoint = this; const dateFormat = HighchartsDateFormatToCommon[dataPoint.dateTimeLabelFormat] || DateFormat.FullDate; - const utcWithOffsetDate = HC_Utilities.getUtcWithOffsetDate(dataPoint.value); - - return chartOptions.dateFormatter(utcWithOffsetDate, dateFormat); + + return chartOptions.dateFormatter(dataPoint.value, dateFormat); } } diff --git a/test/highcharts/chart.test.ts b/test/highcharts/chart.test.ts index d46f54c..4222f1b 100644 --- a/test/highcharts/chart.test.ts +++ b/test/highcharts/chart.test.ts @@ -16,7 +16,7 @@ describe('Unit tests for Chart methods', () => { // Add mock to Utilities.getDateValue -> return the full year jest .spyOn(Utilities, 'getDateValue') - .mockImplementation(function(dateStr, offset) { + .mockImplementation(function(dateStr) { return new Date(dateStr).getFullYear(); });