From fb16f48d74b4d21197146b19de4e8c68a99524a1 Mon Sep 17 00:00:00 2001 From: Lena Date: Mon, 4 May 2020 12:34:57 -0700 Subject: [PATCH] [Chart] Sort Feature (#29) * preserve order for categorical data * y-axis min val is 0 unless specified https://github.com/pinterest/datahub/pull/29 --- datahub/config/datadoc.yaml | 3 ++ .../webapp/components/AppAdmin/AppAdmin.scss | 1 + .../DataDocChartCell/DataDocChartCell.tsx | 8 ++- .../DataDocChartCell/DataDocChartComposer.tsx | 50 +++++++++++++++++-- datahub/webapp/const/dataDocChart.ts | 6 +++ .../webapp/lib/chart/chart-data-processing.ts | 2 +- .../lib/chart/chart-data-transformation.ts | 46 ++++++++++++++--- .../webapp/lib/chart/chart-meta-processing.ts | 11 ++++ datahub/webapp/lib/chart/chart-utils.ts | 12 +++-- 9 files changed, 123 insertions(+), 16 deletions(-) diff --git a/datahub/config/datadoc.yaml b/datahub/config/datadoc.yaml index f3321100d..b3c5d6110 100644 --- a/datahub/config/datadoc.yaml +++ b/datahub/config/datadoc.yaml @@ -42,6 +42,9 @@ cell_types: scale: '' min: 0 max: 0 + sort: + idx: 0 + asc: true y_axis: label: '' scale: '' diff --git a/datahub/webapp/components/AppAdmin/AppAdmin.scss b/datahub/webapp/components/AppAdmin/AppAdmin.scss index 397082df9..aa0f637c8 100644 --- a/datahub/webapp/components/AppAdmin/AppAdmin.scss +++ b/datahub/webapp/components/AppAdmin/AppAdmin.scss @@ -64,6 +64,7 @@ flex-grow: 1; text-align: left; overflow-y: hidden; + overflow-x: hidden; .AdminForm-button-box { display: flex; justify-content: center; diff --git a/datahub/webapp/components/DataDocChartCell/DataDocChartCell.tsx b/datahub/webapp/components/DataDocChartCell/DataDocChartCell.tsx index 20bb8cf0e..c48151d96 100644 --- a/datahub/webapp/components/DataDocChartCell/DataDocChartCell.tsx +++ b/datahub/webapp/components/DataDocChartCell/DataDocChartCell.tsx @@ -129,6 +129,9 @@ export const DataDocChartCell: React.FunctionComponent = ({ formatSeriesCol, formatValueCols, aggSeries, + sortIndex, + sortAsc, + xAxisIdx, } = getDataTransformationOptions(meta); return transformData( @@ -138,7 +141,10 @@ export const DataDocChartCell: React.FunctionComponent = ({ formatAggCol, formatSeriesCol, formatValueCols, - aggSeries + aggSeries, + sortIndex, + sortAsc, + xAxisIdx ); } } diff --git a/datahub/webapp/components/DataDocChartCell/DataDocChartComposer.tsx b/datahub/webapp/components/DataDocChartCell/DataDocChartComposer.tsx index 4b583f75a..00454b099 100644 --- a/datahub/webapp/components/DataDocChartCell/DataDocChartComposer.tsx +++ b/datahub/webapp/components/DataDocChartCell/DataDocChartComposer.tsx @@ -43,7 +43,6 @@ import { Level, LevelItem } from 'ui/Level/Level'; import { SimpleReactSelect } from 'ui/SimpleReactSelect/SimpleReactSelect'; import { getDefaultScaleType } from 'lib/chart/chart-utils'; import { NumberField } from 'ui/FormikField/NumberField'; -import { CheckboxField } from 'ui/FormikField/CheckboxField'; import { ReactSelectField } from 'ui/FormikField/ReactSelectField'; import { FormWrapper } from 'ui/Form/FormWrapper'; import { SimpleField } from 'ui/FormikField/SimpleField'; @@ -109,7 +108,10 @@ const DataDocChartComposerComponent: React.FunctionComponent< values.formatAggCol, values.formatSeriesCol, values.formatValueCols, - values.aggSeries + values.aggSeries, + values.sortIndex, + values.sortAsc, + values.xIndex ) : null; }, [ @@ -121,6 +123,9 @@ const DataDocChartComposerComponent: React.FunctionComponent< values.formatValueCols, values.aggType, values.aggSeries, + values.sortIndex, + values.sortAsc, + values.xIndex, ]); // getting redux state @@ -619,10 +624,40 @@ const DataDocChartComposerComponent: React.FunctionComponent< ); } + const sortDOM = ( + <> + Sort + { + setFieldValue('sortIndex', val); + if (val != null) { + setTableTab('transformed'); + } + }} + /> + + + ); + const chartTabDOM = ( <> {chartOptionsDOM} {axesDOM} + {sortDOM} ); @@ -722,7 +757,7 @@ const DataDocChartComposerComponent: React.FunctionComponent< let dataDOM: JSX.Element; let dataSwitch: JSX.Element; if (chartData && showTable) { - if (values.aggregate || values.switch) { + if (values.aggregate || values.switch || values.sortIndex != null) { dataSwitch = (
{dataSwitch} {hideTableButtonDOM} - {dataDOM}
); @@ -877,12 +911,18 @@ function formValsToMeta(vals: IChartFormValues, meta: IDataChartCellMeta) { for (const [field, val] of Object.entries(vals.xAxis)) { draft.chart.x_axis[field] = val; } - draft.chart.y_axis.stack = vals.stack; + if (vals.sortIndex != null) { + draft.chart.x_axis.sort = { + idx: vals.sortIndex, + asc: vals.sortAsc, + }; + } // Y Axes for (const [field, val] of Object.entries(vals.yAxis)) { draft.chart.y_axis[field] = val; } + draft.chart.y_axis.stack = vals.stack; const seriesObj = {}; if (vals.hiddenSeries.length) { diff --git a/datahub/webapp/const/dataDocChart.ts b/datahub/webapp/const/dataDocChart.ts index 5c593dc10..0058e342b 100644 --- a/datahub/webapp/const/dataDocChart.ts +++ b/datahub/webapp/const/dataDocChart.ts @@ -72,6 +72,10 @@ export interface IChartAxisMeta { export interface IChartXAxisMeta extends IChartAxisMeta { col_idx: number; + sort?: { + idx: number; + asc: boolean; + }; } export interface IChartYAxisMeta extends IChartAxisMeta { @@ -144,6 +148,8 @@ export interface IChartFormValues { xAxis: IChartAxisMeta; xIndex: number; + sortIndex: number | undefined; + sortAsc: boolean; yAxis: IChartAxisMeta; stack: boolean; hiddenSeries: number[]; diff --git a/datahub/webapp/lib/chart/chart-data-processing.ts b/datahub/webapp/lib/chart/chart-data-processing.ts index 12cd57dd4..8ed02261c 100644 --- a/datahub/webapp/lib/chart/chart-data-processing.ts +++ b/datahub/webapp/lib/chart/chart-data-processing.ts @@ -22,7 +22,7 @@ export function processChartJSData( const xAxisIdx = chartMeta.x_axis.col_idx; const seriesNames: string[] = data[0]; - const dataRows = sortTable(data.slice(1), xAxisIdx); + const dataRows = data.slice(1); // hide hidden series const hiddenSeriesIndices = new Set(); diff --git a/datahub/webapp/lib/chart/chart-data-transformation.ts b/datahub/webapp/lib/chart/chart-data-transformation.ts index 482b3538b..effdaad72 100644 --- a/datahub/webapp/lib/chart/chart-data-transformation.ts +++ b/datahub/webapp/lib/chart/chart-data-transformation.ts @@ -1,5 +1,6 @@ import { cloneDeep, range } from 'lodash'; import { ChartDataAggType } from 'const/dataDocChart'; +import { sortTable, getValueDataType } from './chart-utils'; const emptyCellValue = 'No Value'; @@ -227,6 +228,27 @@ function aggregateData( return aggregatedData; } +export function sortTableWithDefaultIdx( + data: any[][], + idx: number, + ascending: boolean, + xIdx: number +) { + if (idx != null) { + // if idx is provided then call sortTable as is + return sortTable(data, idx, ascending); + } else { + // if idx is not provided then default idx to the xIndex of the table + // only sort if the column type for that index is either 'date' 'datetime' or 'number' + const valType = getValueDataType(data[0][xIdx]); + if (valType === 'string' || valType === null) { + return data; + } else { + return sortTable(data, xIdx, ascending); + } + } +} + export function transformData( data: any[][], isAggregate: boolean = false, @@ -236,16 +258,16 @@ export function transformData( formatValueCols: number[] = [2], aggSeries: { [seriesIdx: number]: ChartDataAggType; - } = {} + } = {}, + // tslint:disable-next-line: no-unnecessary-initializer + sortIdx: number = undefined, + sortAsc: boolean = true, + xAxisIdx: number = 0 ) { if (data?.length < 2) { return null; } - if (!isAggregate && !isSwitch) { - return data; - } - let transformedData = cloneDeep(data); if (isAggregate) { @@ -258,9 +280,21 @@ export function transformData( ); } + if (transformedData == null) { + return null; + } + if (isSwitch) { transformedData = switchData(transformedData); } - return transformedData; + return [ + transformedData[0], + ...sortTableWithDefaultIdx( + transformedData.slice(1), + sortIdx, + sortAsc, + xAxisIdx + ), + ]; } diff --git a/datahub/webapp/lib/chart/chart-meta-processing.ts b/datahub/webapp/lib/chart/chart-meta-processing.ts index e7c896701..c7828e80a 100644 --- a/datahub/webapp/lib/chart/chart-meta-processing.ts +++ b/datahub/webapp/lib/chart/chart-meta-processing.ts @@ -63,6 +63,9 @@ export function getDataTransformationOptions(meta: IDataChartCellMeta) { switch: Boolean(transformations.switch), aggSeries, aggType, + sortIndex: meta.chart.x_axis.sort?.idx, + sortAsc: meta.chart.x_axis.sort?.asc ?? true, + xAxisIdx: meta.chart.x_axis.col_idx, }; } @@ -106,6 +109,8 @@ export function mapMetaToFormVals( // axes xAxis: getAxisOptions(meta.chart.x_axis), xIndex: meta.chart.x_axis.col_idx, + sortIndex: meta.chart.x_axis.sort?.idx, + sortAsc: meta.chart.x_axis.sort?.asc ?? true, yAxis: getAxisOptions(meta.chart.y_axis), stack: Boolean(meta.chart.y_axis.stack), @@ -299,6 +304,12 @@ function computeScaleOptions( ...axis.ticks, min: axisMeta.min, }; + } else { + // default min to 0 unless specified + axis.ticks = { + ...axis.ticks, + min: 0, + }; } } diff --git a/datahub/webapp/lib/chart/chart-utils.ts b/datahub/webapp/lib/chart/chart-utils.ts index 5480f387e..2ea5aa781 100644 --- a/datahub/webapp/lib/chart/chart-utils.ts +++ b/datahub/webapp/lib/chart/chart-utils.ts @@ -48,7 +48,8 @@ export function getDefaultScaleType(value: any): ChartScaleType { export function sortTable( tableRows: any[][], - columnIndex: number = 0 + columnIndex: number = 0, + ascending: boolean = true ): any[][] { // Check if string are numbers, if so use number sort // otherwise use default sort @@ -57,10 +58,15 @@ export function sortTable( return tableRows; } + const reverseMultiplier = ascending ? 1 : -1; + if (!isNaN(tableRows[0][columnIndex] as number)) { return tableRows.sort( - (a, b) => a[columnIndex] - b[columnIndex] + (a, b) => (a[columnIndex] - b[columnIndex]) * reverseMultiplier ) as any[]; } - return tableRows.sort((a, b) => (a[columnIndex] > b[columnIndex] ? 1 : -1)); + + return tableRows.sort( + (a, b) => (a[columnIndex] > b[columnIndex] ? 1 : -1) * reverseMultiplier + ); }