/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
frontend/lib/src/components/widgets/DataFrame/columns/ChartColumn.test.ts
370 строк
12 KB
Ken McGrady
Fix bar chart error with uniform column values (#13590)
14 янв 2026, 22:58
Не верифицирован
14 янв 2026, 22:58
9a1d51c
Код
Авторство
О чём код?
/** * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026) * * Licensed 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 { GridCellKind } from "@glideapps/glide-data-grid" import { SparklineCellType } from "@glideapps/glide-data-grid-cells" import { Field, Float64, List } from "apache-arrow" import { DataFrameCellType } from "~lib/dataframes/arrowTypeUtils" import { mockTheme } from "~lib/mocks/mockTheme" import { AREA_CHART_TYPE, AreaChartColumn, BAR_CHART_TYPE, BarChartColumn, ChartColumnParams, LINE_CHART_TYPE, LineChartColumn, } from "./ChartColumn" import { BaseColumnProps, isErrorCell } from "./utils" const CHART_COLUMN_TEMPLATE = { id: "1", name: "chart_column", title: "Chart column", indexNumber: 0, isEditable: false, isHidden: false, isIndex: false, isPinned: false, isStretched: false, arrowType: { type: DataFrameCellType.DATA, arrowField: new Field( "chart_column", new List(new Field("item", new Float64(), true)), true ), pandasType: { field_name: "chart_column", name: "chart_column", pandas_type: "object", numpy_type: "list[float64]", metadata: null, }, }, } as BaseColumnProps function getLineChartColumn( params?: ChartColumnParams ): ReturnType<typeof LineChartColumn> { return LineChartColumn( { ...CHART_COLUMN_TEMPLATE, columnTypeOptions: params, } as BaseColumnProps, mockTheme.emotion ) } function getBarChartColumn( params?: ChartColumnParams ): ReturnType<typeof BarChartColumn> { return BarChartColumn( { ...CHART_COLUMN_TEMPLATE, columnTypeOptions: params, } as BaseColumnProps, mockTheme.emotion ) } function getAreaChartColumn( params?: ChartColumnParams ): ReturnType<typeof AreaChartColumn> { return AreaChartColumn( { ...CHART_COLUMN_TEMPLATE, columnTypeOptions: params, } as BaseColumnProps, mockTheme.emotion ) } describe("ChartColumn", () => { it("creates a valid column instance", () => { const mockColumn = getLineChartColumn() expect(mockColumn.kind).toEqual(LINE_CHART_TYPE) expect(mockColumn.title).toEqual(CHART_COLUMN_TEMPLATE.title) expect(mockColumn.id).toEqual(CHART_COLUMN_TEMPLATE.id) expect(mockColumn.sortMode).toEqual("default") // Column should be readonly: expect(mockColumn.isEditable).toEqual(false) const mockCell = mockColumn.getCell([0.1, 0.2, 0.3]) expect(mockCell.kind).toEqual(GridCellKind.Custom) expect((mockCell as SparklineCellType).data?.values).toEqual([ 0.1, 0.2, 0.3, ]) expect((mockCell as SparklineCellType).data?.displayValues).toEqual([ "0.1", "0.2", "0.3", ]) }) it("supports configuring the chart type", () => { const mockColumn = getLineChartColumn() expect(mockColumn.kind).toEqual(LINE_CHART_TYPE) const mockCell = mockColumn.getCell([0.1, 0.2, 0.3]) // Default chart type is line expect((mockCell as SparklineCellType).data?.graphKind).toEqual("line") const mockBarChartColumn = getBarChartColumn() expect(mockBarChartColumn.kind).toEqual(BAR_CHART_TYPE) const mockBarChartCell = mockBarChartColumn.getCell([0.1, 0.2, 0.3]) // Chart type should be bar expect((mockBarChartCell as SparklineCellType).data?.graphKind).toEqual( "bar" ) const mockAreaChartColumn = getAreaChartColumn() expect(mockAreaChartColumn.kind).toEqual(AREA_CHART_TYPE) const mockAreaChartCell = mockAreaChartColumn.getCell([0.1, 0.2, 0.3]) // Chart type should be area expect((mockAreaChartCell as SparklineCellType).data?.graphKind).toEqual( "area" ) }) it("supports configuring min/max scale", () => { const mockColumn = getLineChartColumn() const mockCell = mockColumn.getCell([-100, 0, 100]) // Default min/max scale is the min and max of each cell, so nothing happens: expect((mockCell as SparklineCellType).data?.values).toEqual([ -100, 0, 100, ]) // Use a different scale const mockColumn1 = getLineChartColumn({ y_min: -100, y_max: 100, }) const mockCell1 = mockColumn1.getCell([-100, 0, 100]) expect((mockCell1 as SparklineCellType).data?.values).toEqual([ -100, 0, 100, ]) // Use a different scale const mockColumn2 = getLineChartColumn({ y_min: -1, y_max: 1, }) const mockCell2 = mockColumn2.getCell([-100, 0, 100]) // This should automatically normalize the values to the min/max scale: expect((mockCell2 as SparklineCellType).data?.values).toEqual([-1, 0, 1]) // Use a different scale const mockColumn3 = getLineChartColumn({ y_min: 0, y_max: 200, }) const mockCell3 = mockColumn3.getCell([-100, 0, 100]) // This should automatically normalize the values to the min/max scale: expect((mockCell3 as SparklineCellType).data?.values).toEqual([ 0, 100, 200, ]) // Use a different scale const mockColumn4 = getLineChartColumn({ y_min: -200, y_max: 200, }) const mockCell4 = mockColumn4.getCell([-100, 0, 100]) // The values fit into the scale, so don't do anything: expect((mockCell4 as SparklineCellType).data?.values).toEqual([ -100, 0, 100, ]) // Use a different scale const mockColumn5 = getLineChartColumn({ y_min: 100, y_max: -100, }) const mockCell5 = mockColumn5.getCell([-100, 0, 100]) // min needs to be bigger than max, so this should be an error cell: expect(isErrorCell(mockCell5)).toEqual(true) // Use a different scale const mockColumn6 = getLineChartColumn({ y_min: undefined, y_max: -100, }) const mockCell6 = mockColumn6.getCell([-100, 0, 100]) // min and max can't be the same number, so this should be an error cell: expect(isErrorCell(mockCell6)).toEqual(true) }) it("supports negative numbers", () => { const mockColumn = getLineChartColumn() const mockCell = mockColumn.getCell([-50, 50, 150]) // The default values for y_min and y_max are -50 and 150, so don't do anything expect((mockCell as SparklineCellType).data?.values).toEqual([ -50, 50, 150, ]) const mockColumn1 = getLineChartColumn() const mockCell1 = mockColumn1.getCell([-50, -40, -30]) // The default values for y_min and y_max are -50 and -30, so don't do anything expect((mockCell1 as SparklineCellType).data?.values).toEqual([ -50, -40, -30, ]) const mockColumn2 = getLineChartColumn({ y_min: undefined, y_max: -60, }) const mockCell2 = mockColumn2.getCell([-50, -40, -30]) // y_max is -60 and the default for y_min is -50, so this should be an error cell: expect(isErrorCell(mockCell2)).toEqual(true) const mockColumn3 = getLineChartColumn() const mockCell3 = mockColumn3.getCell([-50]) // The min/max scale is -50/0 for this case, so don't do anything: expect((mockCell3 as SparklineCellType).data?.values).toEqual([-50]) }) it("works with single values or only same values without running into division by zero", () => { const mockColumn = getBarChartColumn({ y_min: 0, y_max: 100, }) const mockCell1 = mockColumn.getCell([101]) // The value should be normalized to 100: expect((mockCell1 as SparklineCellType).data?.values).toEqual([100]) const mockCell2 = mockColumn.getCell([101, 101]) // All values should be normalized to 100: expect((mockCell2 as SparklineCellType).data?.values).toEqual([100, 100]) const mockCell3 = mockColumn.getCell([-1, -1]) // All values should be normalized to 0: expect((mockCell3 as SparklineCellType).data?.values).toEqual([0, 0]) }) it("handles uniform values without explicit y_min/y_max configuration", () => { // This tests the fix for https://github.com/streamlit/streamlit/issues/13584 const mockColumn = getBarChartColumn() // Uniform positive values should render with y_min=0 and y_max=value const mockCell1 = mockColumn.getCell([4, 4, 4, 4]) expect(isErrorCell(mockCell1)).toEqual(false) expect((mockCell1 as SparklineCellType).data?.values).toEqual([4, 4, 4, 4]) expect((mockCell1 as SparklineCellType).data?.yAxis).toEqual([0, 4]) // Uniform zero values should render with y_min=0 and y_max=1 const mockCell2 = mockColumn.getCell([0, 0, 0]) expect(isErrorCell(mockCell2)).toEqual(false) expect((mockCell2 as SparklineCellType).data?.values).toEqual([0, 0, 0]) expect((mockCell2 as SparklineCellType).data?.yAxis).toEqual([0, 1]) // Uniform negative values should render with y_min=value and y_max=0 const mockCell3 = mockColumn.getCell([-5, -5, -5]) expect(isErrorCell(mockCell3)).toEqual(false) expect((mockCell3 as SparklineCellType).data?.values).toEqual([-5, -5, -5]) expect((mockCell3 as SparklineCellType).data?.yAxis).toEqual([-5, 0]) }) it("supports named color mapping and custom colors", () => { const blueColumn = getLineChartColumn({ color: "blue" }) const blueCell = blueColumn.getCell([0, 1]) as SparklineCellType expect(blueCell.data?.color).toEqual(mockTheme.emotion.colors.blueColor) const greyColumn = getBarChartColumn({ color: "grey" }) const greyCell = greyColumn.getCell([0, 1]) as SparklineCellType expect(greyCell.data?.color).toEqual(mockTheme.emotion.colors.grayColor) const customColor = "#123456" const customColumn = getAreaChartColumn({ color: customColor }) const customCell = customColumn.getCell([0, 1]) as SparklineCellType expect(customCell.data?.color).toEqual(customColor) }) it("applies auto color based on trend", () => { // auto: default is green; red if trend down const autoUp = getLineChartColumn({ color: "auto" }) const cellUp = autoUp.getCell([0, 1]) as SparklineCellType expect(cellUp.data?.color).toEqual(mockTheme.emotion.colors.greenColor) const autoDown = getLineChartColumn({ color: "auto" }) const cellDown = autoDown.getCell([1, 0]) as SparklineCellType expect(cellDown.data?.color).toEqual(mockTheme.emotion.colors.redColor) }) it("applies auto-inverse color based on trend", () => { // auto-inverse: default is green; red if trend up const autoInvDown = getBarChartColumn({ color: "auto-inverse" }) const cellInvDown = autoInvDown.getCell([1, 0]) as SparklineCellType expect(cellInvDown.data?.color).toEqual( mockTheme.emotion.colors.greenColor ) const autoInvUp = getBarChartColumn({ color: "auto-inverse" }) const cellInvUp = autoInvUp.getCell([0, 1]) as SparklineCellType expect(cellInvUp.data?.color).toEqual(mockTheme.emotion.colors.redColor) }) it.each([ // Supports almost the same as toSafeArray [null, null], [undefined, null], ["", null], [[], null], // Comma separated syntax ["0.1,0.2", [0.1, 0.2]], // JSON Array syntax [`["0.1","0.2"]`, [0.1, 0.2]], ["1", [1]], [0, [0]], [1, [1]], [ [0, 0.2, 0.1], [0, 0.2, 0.1], ], [true, [1]], [false, [0]], ])( "supports numerical array-compatible value (%p parsed as %p)", // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: Replace 'any' with a more specific type. (input: any, value: any[] | null) => { const mockColumn = getBarChartColumn() const cell = mockColumn.getCell(input) expect(mockColumn.getCellValue(cell)).toEqual(value) } ) it.each([ ["foo"], ["foo, bar"], ["0.1,0.4,foo"], ["0.1,0.4,"], [["foo", "bar"]], [[0.1, 0.4, "foo"]], [[0.1, 0.4, null]], // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: Replace 'any' with a more specific type. ])("%p results in error cell", (input: any) => { const mockColumn = getLineChartColumn() const cell = mockColumn.getCell(input) expect(isErrorCell(cell)).toEqual(true) }) })