Skip to content

Commit

Permalink
feat: Adds the ECharts Histogram chart (apache#28652)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-s-molina committed Jun 4, 2024
1 parent bc9eab9 commit 896fe85
Show file tree
Hide file tree
Showing 30 changed files with 1,061 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitationsxw
* under the License.
*/
import { PostProcessingHistogram, getColumnLabel } from '@superset-ui/core';
import { PostProcessingFactory } from './types';

/* eslint-disable @typescript-eslint/no-unused-vars */
export const histogramOperator: PostProcessingFactory<
PostProcessingHistogram
> = (formData, queryObject) => {
const { bins, column, cumulative, groupby, normalize } = formData;
const parsedBins = Number.isNaN(Number(bins)) ? 5 : Number(bins);
const parsedColumn = getColumnLabel(column);
const parsedGroupBy = groupby!.map(getColumnLabel);
return {
operation: 'histogram',
options: {
column: parsedColumn,
groupby: parsedGroupBy,
bins: parsedBins,
cumulative,
normalize,
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export { rollingWindowOperator } from './rollingWindowOperator';
export { timeCompareOperator } from './timeCompareOperator';
export { timeComparePivotOperator } from './timeComparePivotOperator';
export { sortOperator } from './sortOperator';
export { histogramOperator } from './histogramOperator';
export { pivotOperator } from './pivotOperator';
export { resampleOperator } from './resampleOperator';
export { renameOperator } from './renameOperator';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export { default as sharedControlComponents } from './components';
export * from './components';
export * from './customControls';
export * from './mixins';
export * from './dndControls';
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,48 @@
* specific language governing permissions and limitations
* under the License.
*/
import { QueryResponse } from '@superset-ui/core';
import { Dataset, isDataset, isQueryResponse } from '../types';
import { GenericDataType, QueryColumn, QueryResponse } from '@superset-ui/core';
import { ColumnMeta, Dataset, isDataset, isQueryResponse } from '../types';

export function columnsByType(
datasource?: Dataset | QueryResponse | null,
type?: GenericDataType,
): (ColumnMeta | QueryColumn)[] {
if (isDataset(datasource) || isQueryResponse(datasource)) {
const columns = datasource.columns as (ColumnMeta | QueryColumn)[];
const filteredColumns = columns.filter(
col => type === undefined || col.type_generic === type,
);
return filteredColumns.sort(
(col1: ColumnMeta | QueryColumn, col2: ColumnMeta | QueryColumn) => {
const opt1Name =
'verbose_name' in col1
? col1.verbose_name || col1.column_name
: col1.column_name;
const opt2Name =
'verbose_name' in col2
? col2.verbose_name || col2.column_name
: col2.column_name;
return opt1Name.toLowerCase() > opt2Name.toLowerCase() ? 1 : -1;
},
);
}
return [];
}

/**
* Convert Datasource columns to column choices
*/
export default function columnChoices(
datasource?: Dataset | QueryResponse | null,
type?: GenericDataType,
): [string, string][] {
if (isDataset(datasource) || isQueryResponse(datasource)) {
return datasource.columns
.map((col): [string, string] => [
col.column_name,
'verbose_name' in col
? col.verbose_name || col.column_name
: col.column_name,
])
.sort((opt1, opt2) =>
opt1[1].toLowerCase() > opt2[1].toLowerCase() ? 1 : -1,
);
}
return [];
return columnsByType(datasource, type).map(
(col: ColumnMeta | QueryColumn): [string, string] => [
col.column_name,
'verbose_name' in col
? col.verbose_name || col.column_name
: col.column_name,
],
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export * from './D3Formatting';
export * from './expandControlConfig';
export * from './getColorFormatters';
export { default as mainMetric } from './mainMetric';
export { default as columnChoices } from './columnChoices';
export { default as columnChoices, columnsByType } from './columnChoices';
export * from './defineSavedMetrics';
export * from './getStandardizedControls';
export * from './getTemporalColumns';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ export function formatSelectOptions<T extends Formattable>(
* >> formatSelectOptionsForRange(1, 5)
* >> [[1,'1'], [2,'2'], [3,'3'], [4,'4'], [5,'5']]
*/
export function formatSelectOptionsForRange(start: number, end: number) {
export function formatSelectOptionsForRange(
start: number,
end: number,
step = 1,
): Formatted[] {
const options: Formatted[] = [];
for (let i = start; i <= end; i += 1) {
for (let i = start; i <= end; i += step) {
options.push([i, i.toString()]);
}
return options;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { histogramOperator } from '@superset-ui/chart-controls';
import { SqlaFormData } from '@superset-ui/core';
import { omit } from 'lodash';

const formData: SqlaFormData = {
bins: 5,
column: 'quantity',
cumulative: true,
normalize: true,
groupby: ['country', 'region'],
viz_type: 'histogram',
datasource: 'foo',
};

test('matches formData', () => {
expect(histogramOperator(formData, {})).toEqual({
operation: 'histogram',
options: omit(formData, ['viz_type', 'datasource']),
});
});

test('defaults to 5 bins', () => {
expect(
histogramOperator(omit(formData, ['bins']) as SqlaFormData, {}),
).toEqual({
operation: 'histogram',
options: omit(formData, ['viz_type', 'datasource']),
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,44 @@ describe('columnChoices()', () => {
['Column 2', 'Column 2'],
['Column 3', 'Column 3'],
]);
expect.anything();
});

it('should return choices of a specific type', () => {
expect(columnChoices(testQueryResponse, GenericDataType.Temporal)).toEqual([
['Column 2', 'Column 2'],
]);
});
it('should use name when verbose_name key exists but is not defined', () => {
expect(
columnChoices({
id: 1,
metrics: [],
type: DatasourceType.Table,
main_dttm_col: 'test',
time_grain_sqla: [],
columns: [
{
column_name: 'foo',
verbose_name: null,
type: 'VARCHAR',
type_generic: GenericDataType.String,
},
{
column_name: 'bar',
verbose_name: null,
type: 'VARCHAR',
type_generic: GenericDataType.String,
},
],
verbose_map: {},
column_formats: { fiz: 'NUMERIC', about: 'STRING', foo: 'DATE' },
currency_formats: {},
datasource_name: 'my_datasource',
description: 'this is my datasource',
}),
).toEqual([
['bar', 'bar'],
['foo', 'foo'],
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,20 @@ interface _PostProcessingRank {
}
export type PostProcessingRank = _PostProcessingRank | DefaultPostProcessing;

interface _PostProcessingHistogram {
operation: 'histogram';
options?: {
column: string;
groupby: string[];
bins: number;
cumulative?: boolean;
normalize?: boolean;
};
}
export type PostProcessingHistogram =
| _PostProcessingHistogram
| DefaultPostProcessing;

/**
* Parameters for chart data postprocessing.
* See superset/utils/pandas_processing.py.
Expand All @@ -251,6 +265,7 @@ export type PostProcessingRule =
| PostProcessingResample
| PostProcessingRename
| PostProcessingFlatten
| PostProcessingHistogram
| PostProcessingRank;

export function isPostProcessingAggregation(
Expand Down Expand Up @@ -318,3 +333,27 @@ export function isPostProcessingResample(
): rule is PostProcessingResample {
return rule?.operation === 'resample';
}

export function isPostProcessingRename(
rule?: PostProcessingRule,
): rule is PostProcessingRename {
return rule?.operation === 'rename';
}

export function isPostProcessingFlatten(
rule?: PostProcessingRule,
): rule is PostProcessingFlatten {
return rule?.operation === 'flatten';
}

export function isPostProcessingRank(
rule?: PostProcessingRule,
): rule is PostProcessingRank {
return rule?.operation === 'rank';
}

export function isPostProcessingHistogram(
rule?: PostProcessingRule,
): rule is PostProcessingHistogram {
return rule?.operation === 'histogram';
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ import {
PostProcessingResample,
PostProcessingRolling,
PostProcessingSort,
PostProcessingHistogram,
isPostProcessingHistogram,
PostProcessingRename,
PostProcessingFlatten,
PostProcessingRank,
isPostProcessingRename,
isPostProcessingFlatten,
isPostProcessingRank,
} from '@superset-ui/core';
import { ComparisonType, RollingType, TimeGranularity } from '../../../src';

Expand Down Expand Up @@ -151,6 +159,38 @@ const SORT_RULE: PostProcessingSort = {
},
};

const HISTOGRAM_RULE: PostProcessingHistogram = {
operation: 'histogram',
options: {
column: 'foo',
groupby: ['bar'],
bins: 5,
normalize: true,
cumulative: true,
},
};

const RENAME_RULE: PostProcessingRename = {
operation: 'rename',
options: {
columns: {
foo: 'bar',
},
},
};

const FLATTEN_RULE: PostProcessingFlatten = {
operation: 'flatten',
};

const RANK_RULE: PostProcessingRank = {
operation: 'rank',
options: {
metric: 'foo',
group_by: 'bar',
},
};

test('PostProcessingAggregation type guard', () => {
expect(isPostProcessingAggregation(AGGREGATE_RULE)).toEqual(true);
expect(isPostProcessingAggregation(BOXPLOT_RULE)).toEqual(false);
Expand Down Expand Up @@ -216,3 +256,27 @@ test('PostProcessingSort type guard', () => {
expect(isPostProcessingSort(AGGREGATE_RULE)).toEqual(false);
expect(isPostProcessingSort(undefined)).toEqual(false);
});

test('PostProcessingHistogram type guard', () => {
expect(isPostProcessingHistogram(HISTOGRAM_RULE)).toEqual(true);
expect(isPostProcessingHistogram(AGGREGATE_RULE)).toEqual(false);
expect(isPostProcessingHistogram(undefined)).toEqual(false);
});

test('PostProcessingRename type guard', () => {
expect(isPostProcessingRename(RENAME_RULE)).toEqual(true);
expect(isPostProcessingRename(AGGREGATE_RULE)).toEqual(false);
expect(isPostProcessingRename(undefined)).toEqual(false);
});

test('PostProcessingFlatten type guard', () => {
expect(isPostProcessingFlatten(FLATTEN_RULE)).toEqual(true);
expect(isPostProcessingFlatten(AGGREGATE_RULE)).toEqual(false);
expect(isPostProcessingFlatten(undefined)).toEqual(false);
});

test('PostProcessingRank type guard', () => {
expect(isPostProcessingRank(RANK_RULE)).toEqual(true);
expect(isPostProcessingRank(AGGREGATE_RULE)).toEqual(false);
expect(isPostProcessingRank(undefined)).toEqual(false);
});
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const metadata = new ChartMetadata({
{ url: example2 },
{ url: example3 },
],
name: t('Histogram'),
name: t('Histogram (legacy)'),
tags: [t('Comparison'), t('Legacy'), t('Pattern'), t('Range')],
thumbnail,
useLegacyApi: true,
Expand Down
Loading

0 comments on commit 896fe85

Please sign in to comment.