diff --git a/src/common/utils/rules/split-adjustments.mocha.ts b/src/common/utils/rules/split-adjustments.mocha.ts new file mode 100644 index 000000000..9fcd2d55e --- /dev/null +++ b/src/common/utils/rules/split-adjustments.mocha.ts @@ -0,0 +1,209 @@ +/* + * Copyright 2017-2021 Allegro.pl + * + * 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 { expect } from "chai"; +import { $ } from "plywood"; +import { createDimension, Dimension } from "../../models/dimension/dimension"; +import { SeriesList } from "../../models/series-list/series-list"; +import { measureSeries } from "../../models/series/series.fixtures"; +import { DimensionSort, SeriesSort, SortDirection } from "../../models/sort/sort"; +import { numberSplitCombine, stringSplitCombine, timeSplitCombine } from "../../models/split/split.fixtures"; +import { + adjustColorSplit, + adjustContinuousTimeSplit, + adjustFiniteLimit, + adjustLimit, + adjustSort +} from "./split-adjustments"; + +describe("Split adjustment utilities", () => { + describe("adjustContinuousTimeSplit", () => { + it("should set limit to null", () => { + const timeSplit = timeSplitCombine("time", undefined, { limit: 500 }); + const adjusted = adjustContinuousTimeSplit(timeSplit); + expect(adjusted.limit).to.be.null; + }); + + it("should set sort to itself", () => { + const timeSplit = timeSplitCombine("time", undefined, { + sort: { + reference: "foobar", + direction: SortDirection.descending + } + }); + const expectedSort = new DimensionSort({ + reference: "time", + direction: SortDirection.ascending + }); + const adjusted = adjustContinuousTimeSplit(timeSplit); + expect(adjusted.sort).to.be.equivalent(expectedSort); + }); + }); + + describe("adjustFiniteLimit", () => { + it("should pass valid limit", () => { + const split = stringSplitCombine("foobar", { limit: 50 }); + const adjusted = adjustFiniteLimit([10, 50, 100])(split); + + expect(adjusted.limit).to.be.equal(50); + }); + + it("should adjust invalid limit", () => { + const split = stringSplitCombine("foobar", { limit: 49 }); + const adjusted = adjustFiniteLimit([10, 50, 100])(split); + + expect(adjusted.limit).to.be.equal(10); + }); + + it("should adjust invalid limit with explicit default", () => { + const split = stringSplitCombine("foobar", { limit: 49 }); + const adjusted = adjustFiniteLimit([10, 50, 100], 42)(split); + + expect(adjusted.limit).to.be.equal(42); + }); + }); + + describe("adjustLimit", () => { + it("should pass valid limit", () => { + const dimension: Dimension = { + ...createDimension("string", "foobar", $("foobar")), + limits: [42, 100] + }; + const split = stringSplitCombine("foobar", { limit: 42 }); + const adjusted = adjustLimit(dimension)(split); + + expect(adjusted.limit).to.be.equal(42); + }); + + it("should adjust limit with dimension limits", () => { + const dimension: Dimension = { + ...createDimension("string", "foobar", $("foobar")), + limits: [42, 100] + }; + const split = stringSplitCombine("foobar", { limit: 50 }); + const adjusted = adjustLimit(dimension)(split); + + expect(adjusted.limit).to.be.equal(42); + }); + + it("should accept null for time dimension", () => { + const dimension: Dimension = { + ...createDimension("time", "time", $("foobar")), + limits: [42, 100] + }; + const split = stringSplitCombine("time", { limit: null }); + const adjusted = adjustLimit(dimension)(split); + + expect(adjusted.limit).to.be.null; + }); + }); + + describe("adjustColorSplit", () => { + it("should adjust limit with predefined limits (5, 10)", () => { + const dimension: Dimension = { + ...createDimension("string", "foobar", $("foobar")), + limits: [42, 100] + }; + const split = stringSplitCombine("foobar", { limit: 50 }); + const adjusted = adjustColorSplit(split, dimension, SeriesList.fromSeries([])); + + expect(adjusted.limit).to.be.equal(10); + }); + }); + + describe("adjustSort", () => { + const series = SeriesList.fromSeries([ + measureSeries("qvux"), + measureSeries("bazz") + ]); + + const foobarDimension = createDimension("string", "foobar", $("foobar")); + + const dimensionSort = (reference: string) => new DimensionSort({ reference, direction: SortDirection.descending }); + const seriesSort = (reference: string) => new SeriesSort({ reference, direction: SortDirection.descending }); + + it("should return back split with series sort", () => { + const split = stringSplitCombine("foobar", { sort: seriesSort("qvux") }); + const adjusted = adjustSort(foobarDimension, series)(split); + + expect(adjusted).to.be.equivalent(split); + }); + + it("should return back split with sort on itself", () => { + const split = stringSplitCombine("foobar", { sort: dimensionSort("foobar") }); + const adjusted = adjustSort(foobarDimension, series)(split); + + expect(adjusted).to.be.equivalent(split); + }); + + it("should return back split with sort on available dimension", () => { + const split = stringSplitCombine("foobar").changeSort(dimensionSort("hodge")); + const adjusted = adjustSort(foobarDimension, series, ["foobar", "hodge"])(split); + + expect(adjusted).to.be.equivalent(split); + }); + + it("should adjust sort on itself for sort strategy 'self'", () => { + const split = stringSplitCombine("foobar").changeSort(dimensionSort("hodge")); + const adjusted = adjustSort({ ...foobarDimension, sortStrategy: "self" }, series)(split); + + expect(adjusted.sort).to.be.equivalent(dimensionSort("foobar")); + }); + + it("should adjust sort on itself for sort strategy equal to itself", () => { + const split = stringSplitCombine("foobar").changeSort(dimensionSort("hodge")); + const adjusted = adjustSort({ ...foobarDimension, sortStrategy: "foobar" }, series)(split); + + expect(adjusted.sort).to.be.equivalent(dimensionSort("foobar")); + }); + + it("should adjust sort on available series according to sort strategy", () => { + const split = stringSplitCombine("foobar").changeSort(dimensionSort("hodge")); + const adjusted = adjustSort({ ...foobarDimension, sortStrategy: "bazz" }, series)(split); + + expect(adjusted.sort).to.be.equivalent(seriesSort("bazz")); + }); + + it("should adjust sort on first series if sort strategy is impossible and is string split", () => { + const split = stringSplitCombine("foobar").changeSort(dimensionSort("hodge")); + const adjusted = adjustSort({ ...foobarDimension, sortStrategy: "dummy-series" }, series)(split); + + expect(adjusted.sort).to.be.equivalent(seriesSort("qvux")); + }); + + it("should adjust sort on itself if sort strategy is impossible and is not string split", () => { + const split = numberSplitCombine("foobar").changeSort(dimensionSort("hodge")); + const adjusted = adjustSort({ ...foobarDimension, sortStrategy: "dummy-series" }, series)(split); + + expect(adjusted.sort).to.be.equivalent(dimensionSort("foobar")); + }); + + it("should adjust sort on first series if no sort strategy is provided and is string split", () => { + const split = stringSplitCombine("foobar").changeSort(dimensionSort("hodge")); + const adjusted = adjustSort({ ...foobarDimension, sortStrategy: "dummy-series" }, series)(split); + + expect(adjusted.sort).to.be.equivalent(seriesSort("qvux")); + }); + + it("should adjust sort on itself if no sort strategy is provided and is not string split", () => { + const split = numberSplitCombine("foobar").changeSort(dimensionSort("hodge")); + const adjusted = adjustSort({ ...foobarDimension, sortStrategy: "dummy-series" }, series)(split); + + expect(adjusted.sort).to.be.equivalent(dimensionSort("foobar")); + }); + }); + +}); diff --git a/src/common/utils/rules/split-adjustments.ts b/src/common/utils/rules/split-adjustments.ts new file mode 100644 index 000000000..bced2a814 --- /dev/null +++ b/src/common/utils/rules/split-adjustments.ts @@ -0,0 +1,92 @@ +/* + * Copyright 2017-2021 Allegro.pl + * + * 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 { NORMAL_COLORS } from "../../models/colors/colors"; +import { Dimension } from "../../models/dimension/dimension"; +import { SeriesList } from "../../models/series-list/series-list"; +import { DimensionSort, SeriesSort, SortDirection } from "../../models/sort/sort"; +import { Split, SplitType } from "../../models/split/split"; +import { thread } from "../functional/functional"; + +const COLORS_COUNT = NORMAL_COLORS.length; + +export function adjustColorSplit(split: Split, dimension: Dimension, series: SeriesList): Split { + return thread( + split, + adjustSort(dimension, series), + // TODO: This magic 5 will disappear in #756 + adjustFiniteLimit([5, COLORS_COUNT], COLORS_COUNT) + ); +} + +export function adjustContinuousTimeSplit(split: Split): Split { + const { reference } = split; + return split + .changeLimit(null) + .changeSort(new DimensionSort({ + reference, + direction: SortDirection.ascending + })); +} + +export function adjustLimit({ kind, limits }: Dimension) { + const isTimeSplit = kind === "time"; + const availableLimits = isTimeSplit ? [...limits, null] : limits; + return adjustFiniteLimit(availableLimits); +} + +export function adjustFiniteLimit(availableLimits: number[], defaultLimit = availableLimits[0]) { + return function(split: Split): Split { + const { limit } = split; + return availableLimits.indexOf(limit) === -1 + ? split.changeLimit(defaultLimit) + : split; + }; +} + +export function adjustSort(dimension: Dimension, series: SeriesList, availableDimensions = [dimension.name]) { + return function(split: Split): Split { + const { sort } = split; + if (sort instanceof SeriesSort) return split; + if (availableDimensions.indexOf(sort.reference) !== -1) return split; + const direction = SortDirection.descending; + const { sortStrategy } = dimension; + if (sortStrategy) { + if (sortStrategy === "self" || split.reference === sortStrategy) { + return split.changeSort(new DimensionSort({ + reference: split.reference, + direction + })); + } + if (series.hasMeasureSeries(sortStrategy)) { + return split.changeSort(new SeriesSort({ + reference: sortStrategy, + direction + })); + } + } + if (split.type === SplitType.string) { + return split.changeSort(new SeriesSort({ + reference: series.series.first().reference, + direction: SortDirection.descending + })); + } + return split.changeSort(new DimensionSort({ + reference: split.reference, + direction + })); + }; +} diff --git a/src/common/visualization-manifests/bar-chart/bar-chart.ts b/src/common/visualization-manifests/bar-chart/bar-chart.ts index f559007cf..73e6f1efe 100644 --- a/src/common/visualization-manifests/bar-chart/bar-chart.ts +++ b/src/common/visualization-manifests/bar-chart/bar-chart.ts @@ -16,50 +16,22 @@ */ import { List } from "immutable"; -import { clamp } from "../../../client/utils/dom/dom"; -import { NORMAL_COLORS } from "../../models/colors/colors"; -import { canBucketByDefault, Dimension } from "../../models/dimension/dimension"; +import { Dimension } from "../../models/dimension/dimension"; import { allDimensions, findDimensionByName } from "../../models/dimension/dimensions"; -import { DimensionSort, SortDirection } from "../../models/sort/sort"; -import { Split } from "../../models/split/split"; +import { Split, SplitType } from "../../models/split/split"; import { Splits } from "../../models/splits/splits"; -import { NORMAL_PRIORITY_ACTION, Resolve, VisualizationManifest } from "../../models/visualization-manifest/visualization-manifest"; +import { + NORMAL_PRIORITY_ACTION, + Resolve, + VisualizationManifest +} from "../../models/visualization-manifest/visualization-manifest"; import { emptySettingsConfig } from "../../models/visualization-settings/empty-settings-config"; +import { thread } from "../../utils/functional/functional"; import { Actions } from "../../utils/rules/actions"; import { Predicates } from "../../utils/rules/predicates"; +import { adjustColorSplit, adjustContinuousTimeSplit, adjustFiniteLimit, adjustSort } from "../../utils/rules/split-adjustments"; import { visualizationDependentEvaluatorBuilder } from "../../utils/rules/visualization-dependent-evaluator"; -function isNominalSplitValid(nominalSplit: Split): boolean { - return nominalSplit.limit !== null && nominalSplit.limit <= NORMAL_COLORS.length; -} - -function isTimeSplitValid(timeSplit: Split): boolean { - const sortByTime = new DimensionSort({ - reference: timeSplit.reference, - direction: SortDirection.ascending - }); - - const isTimeSortValid = timeSplit.sort.equals(sortByTime); - const isTimeLimitValid = timeSplit.limit === null; - - return isTimeSortValid && isTimeLimitValid; -} - -// TODO: This magic 5 will disappear in #756 -const clampNominalSplitLimit = (split: Split) => split - .update("limit", limit => - clamp(limit, 5, NORMAL_COLORS.length)); - -const fixTimeSplit = (split: Split) => { - const { reference } = split; - return split - .changeLimit(null) - .changeSort(new DimensionSort({ - reference, - direction: SortDirection.ascending - })); -}; - const rulesEvaluator = visualizationDependentEvaluatorBuilder .when(Predicates.noSplits()) .then(Actions.manualDimensionSelection("The Bar Chart requires at least one split")) @@ -67,84 +39,73 @@ const rulesEvaluator = visualizationDependentEvaluatorBuilder .when(Predicates.areExactSplitKinds("time")) .then(({ splits, isSelectedVisualization }) => { const timeSplit = splits.getSplit(0); - if (isTimeSplitValid(timeSplit)) return Resolve.ready(isSelectedVisualization ? 10 : 3); + const newTimeSplit = adjustContinuousTimeSplit(timeSplit); + if (timeSplit.equals(newTimeSplit)) return Resolve.ready(isSelectedVisualization ? 10 : 3); return Resolve.automatic(6, { splits: new Splits({ - splits: List([ - fixTimeSplit(timeSplit) - ]) + splits: List([newTimeSplit]) }) }); }) .when(Predicates.areExactSplitKinds("time", "*")) - .then(({ splits }) => { + .then(({ splits, series, dataCube }) => { const timeSplit = splits.getSplit(0); const nominalSplit = splits.getSplit(1); + const nominalDimension = findDimensionByName(dataCube.dimensions, nominalSplit.reference); return Resolve.automatic(6, { // Switch splits in place and conform splits: new Splits({ splits: List([ - clampNominalSplitLimit(nominalSplit), - fixTimeSplit(timeSplit) + adjustColorSplit(nominalSplit, nominalDimension, series), + adjustContinuousTimeSplit(timeSplit) ]) }) }); }) .when(Predicates.areExactSplitKinds("*", "time")) - .then(({ splits, isSelectedVisualization }) => { - const nominalSplit = splits.getSplit(0); + .then(({ splits, series, dataCube, isSelectedVisualization }) => { const timeSplit = splits.getSplit(1); + const nominalSplit = splits.getSplit(0); + const nominalDimension = findDimensionByName(dataCube.dimensions, nominalSplit.reference); + + const newSplits = new Splits({ + splits: List([ + adjustColorSplit(nominalSplit, nominalDimension, series), + adjustContinuousTimeSplit(timeSplit) + ]) + }); - if (isTimeSplitValid(timeSplit) && isNominalSplitValid(nominalSplit)) return Resolve.ready(isSelectedVisualization ? 10 : 3); + const changed = !splits.equals(newSplits); + if (!changed) return Resolve.ready(isSelectedVisualization ? 10 : 3); return Resolve.automatic(6, { - splits: new Splits({ - splits: List([ - clampNominalSplitLimit(nominalSplit), - fixTimeSplit(timeSplit) - ]) - }) + splits: newSplits }); }) .when(Predicates.areExactSplitKinds("*")) .or(Predicates.areExactSplitKinds("*", "*")) - .then(({ splits, dataCube, isSelectedVisualization }) => { - let continuousBoost = 0; - - // Auto adjustment - let autoChanged = false; + .then(({ splits, series, dataCube, isSelectedVisualization }) => { + const hasNumberSplits = splits.splits.some(split => split.type === SplitType.number); + const continuousBoost = hasNumberSplits ? 4 : 0; const newSplits = splits.update("splits", splits => splits.map((split: Split) => { const splitDimension = findDimensionByName(dataCube.dimensions, split.reference); - if (canBucketByDefault(splitDimension) && split.sort.reference !== splitDimension.name) { - split = split.changeSort(new DimensionSort({ - reference: splitDimension.name, - direction: split.sort.direction - })); - autoChanged = true; - } - - if (splitDimension.kind === "number") { - continuousBoost = 4; - } - - // ToDo: review this - if (!split.limit && (autoChanged || splitDimension.kind !== "time")) { - split = split.changeLimit(25); - autoChanged = true; - } - - return split; + return thread( + split, + adjustFiniteLimit(splitDimension.limits), + adjustSort(splitDimension, series) + ); })); - if (autoChanged) { + const changed = !splits.equals(newSplits); + if (changed) { return Resolve.automatic(5 + continuousBoost, { splits: newSplits }); } - return Resolve.ready(isSelectedVisualization ? 10 : (7 + continuousBoost)); + return Resolve.ready(isSelectedVisualization ? 10 : 7 + continuousBoost); }) .otherwise(({ dataCube }) => { diff --git a/src/common/visualization-manifests/grid/grid.ts b/src/common/visualization-manifests/grid/grid.ts index 6ab9b9a14..ed2fa79d7 100644 --- a/src/common/visualization-manifests/grid/grid.ts +++ b/src/common/visualization-manifests/grid/grid.ts @@ -14,10 +14,13 @@ * limitations under the License. */ +import { findDimensionByName } from "../../models/dimension/dimensions"; import { Resolve, VisualizationManifest } from "../../models/visualization-manifest/visualization-manifest"; import { emptySettingsConfig } from "../../models/visualization-settings/empty-settings-config"; +import { thread } from "../../utils/functional/functional"; import { Actions } from "../../utils/rules/actions"; import { Predicates } from "../../utils/rules/predicates"; +import { adjustFiniteLimit, adjustSort } from "../../utils/rules/split-adjustments"; import { visualizationDependentEvaluatorBuilder } from "../../utils/rules/visualization-dependent-evaluator"; export const GRID_LIMITS = [50, 100, 200, 500, 1000, 10000]; @@ -29,12 +32,17 @@ const rulesEvaluator = visualizationDependentEvaluatorBuilder .when(Predicates.noSelectedMeasures()) .then(Actions.manualMeasuresSelection()) - .otherwise(({ isSelectedVisualization, splits }) => { + .otherwise(({ isSelectedVisualization, series, dataCube, splits }) => { const firstSplit = splits.getSplit(0); - const { limit: firstLimit } = firstSplit; - const safeFirstLimit = GRID_LIMITS.indexOf(firstLimit) === -1 ? GRID_LIMITS[0] : firstLimit; - const newSplits = splits.replace(firstSplit, firstSplit.changeLimit(safeFirstLimit)); + const splitReferences = splits.splits.toArray().map(split => split.reference); + const dimension = findDimensionByName(dataCube.dimensions, firstSplit.reference); + const fixedFirstSplit = thread( + firstSplit, + adjustFiniteLimit(GRID_LIMITS), + adjustSort(dimension, series, splitReferences) + ); + const newSplits = splits.replace(firstSplit, fixedFirstSplit); if (splits.equals(newSplits)) { return Resolve.ready(isSelectedVisualization ? 10 : 4); diff --git a/src/common/visualization-manifests/heat-map/heat-map.ts b/src/common/visualization-manifests/heat-map/heat-map.ts index 10f0ec06e..c91ce9b49 100644 --- a/src/common/visualization-manifests/heat-map/heat-map.ts +++ b/src/common/visualization-manifests/heat-map/heat-map.ts @@ -18,11 +18,12 @@ import { allDimensions, findDimensionByName } from "../../models/dimension/dimensions"; import { allMeasures } from "../../models/measure/measures"; import { MeasureSeries } from "../../models/series/measure-series"; -import { DimensionSort, isSortEmpty, SeriesSort, SortDirection } from "../../models/sort/sort"; -import { Split, SplitType } from "../../models/split/split"; +import { Split } from "../../models/split/split"; import { Resolve, VisualizationManifest } from "../../models/visualization-manifest/visualization-manifest"; import { emptySettingsConfig } from "../../models/visualization-settings/empty-settings-config"; +import { thread } from "../../utils/functional/functional"; import { Predicates } from "../../utils/rules/predicates"; +import { adjustLimit, adjustSort } from "../../utils/rules/split-adjustments"; import { ActionVariables, visualizationDependentEvaluatorBuilder @@ -42,49 +43,20 @@ const rulesEvaluator = visualizationDependentEvaluatorBuilder variables.series.series.size === 0 ? suggestAddingMeasure(variables) : suggestRemovingMeasures(variables) )) .otherwise(({ splits, dataCube, series }) => { - let autoChanged = false; const newSplits = splits.update("splits", splits => splits.map(split => { const splitDimension = findDimensionByName(dataCube.dimensions, split.reference); - const sortStrategy = splitDimension.sortStrategy; - if (isSortEmpty(split.sort)) { - if (sortStrategy) { - if (sortStrategy === "self" || split.reference === sortStrategy) { - split = split.changeSort(new DimensionSort({ - reference: splitDimension.name, - direction: SortDirection.descending - })); - } else { - split = split.changeSort(new SeriesSort({ - reference: sortStrategy, - direction: SortDirection.descending - })); - } - } else { - if (split.type === SplitType.string) { - split = split.changeSort(new SeriesSort({ - reference: series.series.first().reference, - direction: SortDirection.descending - })); - } else { - split = split.changeSort(new DimensionSort({ - reference: splitDimension.name, - direction: SortDirection.descending - })); - } - autoChanged = true; - } - } - - if (!split.limit && splitDimension.kind !== "time") { - split = split.changeLimit(25); - autoChanged = true; - } - - return split; + return thread( + split, + adjustLimit(splitDimension), + adjustSort(splitDimension, series) + ); })); - return autoChanged ? Resolve.automatic(10, { splits: newSplits }) : Resolve.ready(10); + const changed = !newSplits.equals(splits); + return changed + ? Resolve.automatic(10, { splits: newSplits }) + : Resolve.ready(10); }) .build(); diff --git a/src/common/visualization-manifests/line-chart/line-chart.ts b/src/common/visualization-manifests/line-chart/line-chart.ts index 1b2edf824..a388c1f4b 100644 --- a/src/common/visualization-manifests/line-chart/line-chart.ts +++ b/src/common/visualization-manifests/line-chart/line-chart.ts @@ -16,19 +16,32 @@ */ import { List } from "immutable"; -import { clamp } from "../../../client/utils/dom/dom"; -import { NORMAL_COLORS } from "../../models/colors/colors"; import { getDimensionsByKind } from "../../models/data-cube/data-cube"; +import { Dimension } from "../../models/dimension/dimension"; import { findDimensionByName } from "../../models/dimension/dimensions"; -import { DimensionSort, Sort, SortDirection } from "../../models/sort/sort"; +import { DimensionSort, SortDirection } from "../../models/sort/sort"; import { Split } from "../../models/split/split"; import { Splits } from "../../models/splits/splits"; -import { NORMAL_PRIORITY_ACTION, Resolve, VisualizationManifest } from "../../models/visualization-manifest/visualization-manifest"; +import { + NORMAL_PRIORITY_ACTION, + Resolve, + VisualizationManifest +} from "../../models/visualization-manifest/visualization-manifest"; +import { thread } from "../../utils/functional/functional"; import { Predicates } from "../../utils/rules/predicates"; +import { adjustColorSplit, adjustContinuousTimeSplit, adjustFiniteLimit } from "../../utils/rules/split-adjustments"; import { visualizationDependentEvaluatorBuilder } from "../../utils/rules/visualization-dependent-evaluator"; import { settings } from "./settings"; -const COLORS_COUNT = NORMAL_COLORS.length; +function fixNumberSplit(split: Split, dimension: Dimension): Split { + return thread( + split.changeSort(new DimensionSort({ + reference: split.reference, + direction: SortDirection.ascending + })), + adjustFiniteLimit(dimension.limits) + ); +} const rulesEvaluator = visualizationDependentEvaluatorBuilder .when(({ dataCube }) => !(getDimensionsByKind(dataCube, "time").length || getDimensionsByKind(dataCube, "number").length)) @@ -50,112 +63,68 @@ const rulesEvaluator = visualizationDependentEvaluatorBuilder }) .when(Predicates.areExactSplitKinds("time")) - .or(Predicates.areExactSplitKinds("number")) + .then(({ splits, isSelectedVisualization }) => { + const timeSplit = splits.getSplit(0); + const newTimeSplit = adjustContinuousTimeSplit(timeSplit); + if (timeSplit.equals(newTimeSplit)) return Resolve.ready(isSelectedVisualization ? 10 : 7); + return Resolve.automatic(7, { splits: new Splits({ splits: List([newTimeSplit]) }) }); + }) + .when(Predicates.areExactSplitKinds("number")) .then(({ splits, dataCube, isSelectedVisualization }) => { - let score = 4; - - let continuousSplit = splits.getSplit(0); - const continuousDimension = findDimensionByName(dataCube.dimensions, continuousSplit.reference); - const sortStrategy = continuousDimension.sortStrategy; - - let sort: Sort = null; - if (sortStrategy && sortStrategy !== "self") { - sort = new DimensionSort({ - reference: sortStrategy, - direction: SortDirection.ascending - }); - } else { - sort = new DimensionSort({ - reference: continuousDimension.name, - direction: SortDirection.ascending - }); - } - - let autoChanged = false; - - // Fix time sort - if (!sort.equals(continuousSplit.sort)) { - continuousSplit = continuousSplit.changeSort(sort); - autoChanged = true; - } - - // Fix time limit - if (continuousSplit.limit && continuousDimension.kind === "time") { - continuousSplit = continuousSplit.changeLimit(null); - autoChanged = true; - } - - if (continuousDimension.kind === "time") score += 3; - - if (!autoChanged) return Resolve.ready(isSelectedVisualization ? 10 : score); - return Resolve.automatic(score, { splits: new Splits({ splits: List([continuousSplit]) }) }); + const numberSplit = splits.getSplit(0); + const dimension = findDimensionByName(dataCube.dimensions, numberSplit.reference); + + const newContinuousSplit = fixNumberSplit(numberSplit, dimension); + + if (newContinuousSplit.equals(numberSplit)) return Resolve.ready(isSelectedVisualization ? 10 : 4); + return Resolve.automatic(4, { splits: new Splits({ splits: List([numberSplit]) }) }); }) .when(Predicates.areExactSplitKinds("time", "*")) - .then(({ splits, dataCube }) => { - let timeSplit = splits.getSplit(0); - const timeDimension = findDimensionByName(dataCube.dimensions, timeSplit.reference); + .then(({ splits, series, dataCube }) => { + const timeSplit = splits.getSplit(0); - const sort: Sort = new DimensionSort({ - reference: timeDimension.name, - direction: SortDirection.ascending - }); + const newTimeSplit = timeSplit + .changeSort(new DimensionSort({ reference: timeSplit.reference, direction: SortDirection.ascending })) + .changeLimit(null); - // Fix time sort - if (!sort.equals(timeSplit.sort)) { - timeSplit = timeSplit.changeSort(sort); - } + const colorSplit = splits.getSplit(1); + const colorDimension = findDimensionByName(dataCube.dimensions, colorSplit.reference); - // Fix time limit - if (timeSplit.limit) { - timeSplit = timeSplit.changeLimit(null); - } - - // TODO: This magic 5 will disappear in #756 - const colorSplit = splits.getSplit(1).update("limit", limit => clamp(limit, 5, COLORS_COUNT)); + const newColorSplit = adjustColorSplit(colorSplit, colorDimension, series); return Resolve.automatic(8, { - splits: new Splits({ splits: List([colorSplit, timeSplit]) }) + splits: new Splits({ splits: List([newColorSplit, newTimeSplit]) }) }); }) .when(Predicates.areExactSplitKinds("*", "time")) - .or(Predicates.areExactSplitKinds("*", "number")) - .then(({ splits, dataCube }) => { - let timeSplit = splits.getSplit(1); - const timeDimension = findDimensionByName(dataCube.dimensions, timeSplit.reference); + .then(({ splits, series, dataCube }) => { + const timeSplit = splits.getSplit(1); + const newTimeSplit = adjustContinuousTimeSplit(timeSplit); - let autoChanged = false; + const colorSplit = splits.getSplit(0); + const colorDimension = findDimensionByName(dataCube.dimensions, colorSplit.reference); + const newColorSplit = adjustColorSplit(colorSplit, colorDimension, series); - const sort: Sort = new DimensionSort({ - reference: timeDimension.name, - direction: SortDirection.ascending - }); + const newSplits = new Splits({ splits: List([newColorSplit, newTimeSplit]) }); + if (newSplits.equals(splits)) return Resolve.ready(10); + return Resolve.automatic(8, { splits: newSplits }); + }) + .when(Predicates.areExactSplitKinds("*", "number")) + .then(({ splits, dataCube, series }) => { + const numberSplit = splits.getSplit(1); + const numberDimension = findDimensionByName(dataCube.dimensions, numberSplit.reference); - // Fix time sort - if (!sort.equals(timeSplit.sort)) { - timeSplit = timeSplit.changeSort(sort); - autoChanged = true; - } - - // Fix time limit - if (timeSplit.limit) { - timeSplit = timeSplit.changeLimit(null); - autoChanged = true; - } - - const colorSplit = splits.getSplit(0).update("limit", limit => { - if (limit === null || limit > COLORS_COUNT) { - autoChanged = true; - return COLORS_COUNT; - } - return limit; - }); + const newNumberSplit = fixNumberSplit(numberSplit, numberDimension); - if (!autoChanged) return Resolve.ready(10); - return Resolve.automatic(8, { - splits: new Splits({ splits: List([colorSplit, timeSplit]) }) - }); + const colorSplit = splits.getSplit(0); + const colorDimension = findDimensionByName(dataCube.dimensions, colorSplit.reference); + const newColorSplit = adjustColorSplit(colorSplit, colorDimension, series); + + const newSplits = new Splits({ splits: List([newColorSplit, newNumberSplit]) }); + if (newSplits.equals(splits)) return Resolve.ready(10); + return Resolve.automatic(8, { splits: newSplits }); }) .when(Predicates.haveAtLeastSplitKinds("time")) diff --git a/src/common/visualization-manifests/table/table.ts b/src/common/visualization-manifests/table/table.ts index 362b5f093..fbbbc1e87 100644 --- a/src/common/visualization-manifests/table/table.ts +++ b/src/common/visualization-manifests/table/table.ts @@ -17,8 +17,10 @@ import { findDimensionByName } from "../../models/dimension/dimensions"; import { Resolve, VisualizationManifest } from "../../models/visualization-manifest/visualization-manifest"; +import { threadConditionally } from "../../utils/functional/functional"; import { Actions } from "../../utils/rules/actions"; import { Predicates } from "../../utils/rules/predicates"; +import { adjustLimit, adjustSort } from "../../utils/rules/split-adjustments"; import { visualizationDependentEvaluatorBuilder } from "../../utils/rules/visualization-dependent-evaluator"; import { settings, TableSettings } from "./settings"; @@ -28,21 +30,21 @@ const rulesEvaluator = visualizationDependentEvaluatorBuilder .when(Predicates.supportedSplitsCount()) .then(Actions.removeExcessiveSplits("Table")) - .otherwise(({ splits, dataCube, isSelectedVisualization }) => { - let autoChanged = false; - const newSplits = splits.update("splits", splits => splits.map((split, i) => { + .otherwise(({ splits, dataCube, series, isSelectedVisualization }) => { + const newSplits = splits.update("splits", splits => splits.map(split => { const splitDimension = findDimensionByName(dataCube.dimensions, split.reference); - // ToDo: review this - if (!split.limit && splitDimension.kind !== "time") { - split = split.changeLimit(i ? 5 : 50); - autoChanged = true; - } - - return split; + return threadConditionally( + split, + adjustLimit(splitDimension), + adjustSort(splitDimension, series) + ); })); - return autoChanged ? Resolve.automatic(6, { splits: newSplits }) : Resolve.ready(isSelectedVisualization ? 10 : 6); + const changed = !newSplits.equals(splits); + return changed + ? Resolve.automatic(6, { splits: newSplits }) + : Resolve.ready(isSelectedVisualization ? 10 : 6); }) .build();