From 488da6040514ce83693f75241e959f062d28e8ca Mon Sep 17 00:00:00 2001 From: Kasia Zadurska Date: Tue, 8 Feb 2022 16:19:18 +0100 Subject: [PATCH] Add scatterplot scaffold (#827) Enable choosing scatterplot in menu --- package.json | 1 + .../vis-selector/vis-selector-menu.tsx | 2 + ...s-scatter-plot.svg => vis-scatterplot.svg} | 6 +- .../settings-component.ts | 4 +- src/client/visualizations/index.ts | 4 +- .../scatterplot/scatterplot.scss | 23 +++++ .../scatterplot/scatterplot.tsx | 39 ++++++++ src/common/models/series-list/series-list.ts | 8 ++ .../visualization-manifest.ts | 2 +- src/common/visualization-manifests/index.ts | 4 +- .../scatterplot/scatterplot.ts | 95 +++++++++++++++++++ 11 files changed, 181 insertions(+), 7 deletions(-) rename src/client/icons/{vis-scatter-plot.svg => vis-scatterplot.svg} (94%) create mode 100644 src/client/visualizations/scatterplot/scatterplot.scss create mode 100644 src/client/visualizations/scatterplot/scatterplot.tsx create mode 100644 src/common/visualization-manifests/scatterplot/scatterplot.ts diff --git a/package.json b/package.json index efd37dffe..637f35ffc 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "tsc:watch": "tsc -p tsconfig.json -w --pretty", "lint": "npm-run-all -p lint:*", "lint:ts": "tslint -p . --format verbose", + "lint:ts:fix":"tslint -p . --fix", "lint:sass": "sass-lint -v", "e2e": "start-server-and-test start:examples http://localhost:9090/health/ready 'cypress run'", "e2e:dev": "cypress open", diff --git a/src/client/components/vis-selector/vis-selector-menu.tsx b/src/client/components/vis-selector/vis-selector-menu.tsx index b69d54b4f..5394948b4 100644 --- a/src/client/components/vis-selector/vis-selector-menu.tsx +++ b/src/client/components/vis-selector/vis-selector-menu.tsx @@ -96,6 +96,8 @@ export class VisSelectorMenu extends React.Component}/>; + case "scatterplot": + return null; } } diff --git a/src/client/icons/vis-scatter-plot.svg b/src/client/icons/vis-scatterplot.svg similarity index 94% rename from src/client/icons/vis-scatter-plot.svg rename to src/client/icons/vis-scatterplot.svg index db445bb80..9a83347d3 100644 --- a/src/client/icons/vis-scatter-plot.svg +++ b/src/client/icons/vis-scatterplot.svg @@ -1,11 +1,11 @@ - vis-scatter-plot + vis-scatterplot Created with Sketch. - + @@ -27,4 +27,4 @@ - \ No newline at end of file + diff --git a/src/client/visualization-settings/settings-component.ts b/src/client/visualization-settings/settings-component.ts index 4a239c215..ab43bf90e 100644 --- a/src/client/visualization-settings/settings-component.ts +++ b/src/client/visualization-settings/settings-component.ts @@ -25,6 +25,7 @@ interface SettingsComponents { "heatmap": null; "grid": null; "totals": null; + "scatterplot": null; } const Components: SettingsComponents = { @@ -33,7 +34,8 @@ const Components: SettingsComponents = { "heatmap": null, "grid": null, "totals": null, - "table": TableSettingsComponent + "table": TableSettingsComponent, + "scatterplot": null }; export function settingsComponent(visualization: T): SettingsComponents[T] { diff --git a/src/client/visualizations/index.ts b/src/client/visualizations/index.ts index ac1f1ae51..8a5bb39c1 100644 --- a/src/client/visualizations/index.ts +++ b/src/client/visualizations/index.ts @@ -20,6 +20,7 @@ import { BarChartVisualization } from "./bar-chart/bar-chart"; import { GridVisualization } from "./grid/grid"; import { HeatMapVisualization } from "./heat-map/heat-map"; import { LineChartVisualization } from "./line-chart/line-chart"; +import { ScatterplotVisualization } from "./scatterplot/scatterplot"; import { TableVisualization } from "./table/table"; import { TotalsVisualization } from "./totals/totals"; @@ -29,7 +30,8 @@ const VISUALIZATIONS = { "line-chart": LineChartVisualization, "bar-chart": BarChartVisualization, "heatmap": HeatMapVisualization, - "grid": GridVisualization + "grid": GridVisualization, + "scatterplot": ScatterplotVisualization }; export function getVisualizationComponent({ name }: VisualizationManifest) { diff --git a/src/client/visualizations/scatterplot/scatterplot.scss b/src/client/visualizations/scatterplot/scatterplot.scss new file mode 100644 index 000000000..7e458ac28 --- /dev/null +++ b/src/client/visualizations/scatterplot/scatterplot.scss @@ -0,0 +1,23 @@ +/* + * Copyright 2017-2022 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. + */ +.scatterplot { + .scatterplot-container { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + } +} diff --git a/src/client/visualizations/scatterplot/scatterplot.tsx b/src/client/visualizations/scatterplot/scatterplot.tsx new file mode 100644 index 000000000..4af344449 --- /dev/null +++ b/src/client/visualizations/scatterplot/scatterplot.tsx @@ -0,0 +1,39 @@ +/* + * Copyright 2017-2022 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 * as React from "react"; + +import { ChartProps } from "../../../common/models/chart-props/chart-props"; +import makeQuery from "../../../common/utils/query/visualization-query"; +import { + ChartPanel, + DefaultVisualizationControls, + VisualizationProps +} from "../../views/cube-view/center-panel/center-panel"; + +import "./scatterplot.scss"; + +const Scatterplot: React.SFC = () => { + return
+

Scatterplot will be here

+
; +}; + +export function ScatterplotVisualization(props: VisualizationProps) { + return + + + ; +} diff --git a/src/common/models/series-list/series-list.ts b/src/common/models/series-list/series-list.ts index 86319c194..b116a22aa 100644 --- a/src/common/models/series-list/series-list.ts +++ b/src/common/models/series-list/series-list.ts @@ -140,10 +140,18 @@ export class SeriesList extends Record(defaultSeriesList) { return this.updateSeries(series => series.take(1)); } + public takeNFirst(number: number) { + return this.updateSeries(series => series.take(number)); + } + public getExpressionSeriesFor(reference: string): List { return this.series.filter(series => series.reference === reference && series instanceof ExpressionSeries) as List; } + + public getSeriesKeys(): List { + return this.series.map(series => series.key()); + } } export const EMPTY_SERIES = new SeriesList({ series: List([]) }); diff --git a/src/common/models/visualization-manifest/visualization-manifest.ts b/src/common/models/visualization-manifest/visualization-manifest.ts index 562aee464..8d7c7c3b3 100644 --- a/src/common/models/visualization-manifest/visualization-manifest.ts +++ b/src/common/models/visualization-manifest/visualization-manifest.ts @@ -90,7 +90,7 @@ export class Resolve { } } -export type Visualization = "heatmap" | "table" | "totals" | "bar-chart" | "line-chart" | "grid"; +export type Visualization = "heatmap" | "table" | "totals" | "bar-chart" | "line-chart" | "grid" | "scatterplot"; export class VisualizationManifest { constructor( diff --git a/src/common/visualization-manifests/index.ts b/src/common/visualization-manifests/index.ts index ade5f60e7..183e8f7e0 100644 --- a/src/common/visualization-manifests/index.ts +++ b/src/common/visualization-manifests/index.ts @@ -21,6 +21,7 @@ import { BAR_CHART_MANIFEST } from "./bar-chart/bar-chart"; import { GRID_MANIFEST } from "./grid/grid"; import { HEAT_MAP_MANIFEST } from "./heat-map/heat-map"; import { LINE_CHART_MANIFEST } from "./line-chart/line-chart"; +import { SCATTERPLOT_MANIFEST } from "./scatterplot/scatterplot"; import { TABLE_MANIFEST } from "./table/table"; import { TOTALS_MANIFEST } from "./totals/totals"; @@ -30,7 +31,8 @@ export const MANIFESTS: VisualizationManifest[] = [ LINE_CHART_MANIFEST, BAR_CHART_MANIFEST, HEAT_MAP_MANIFEST, - TABLE_MANIFEST + TABLE_MANIFEST, + SCATTERPLOT_MANIFEST ]; export function manifestByName(visualizationName: string): VisualizationManifest { diff --git a/src/common/visualization-manifests/scatterplot/scatterplot.ts b/src/common/visualization-manifests/scatterplot/scatterplot.ts new file mode 100644 index 000000000..d772954e8 --- /dev/null +++ b/src/common/visualization-manifests/scatterplot/scatterplot.ts @@ -0,0 +1,95 @@ +/* + * Copyright 2017-2022 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 { allDimensions } from "../../models/dimension/dimensions"; +import { allMeasures } from "../../models/measure/measures"; +import { MeasureSeries } from "../../models/series/measure-series"; +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 { Predicates } from "../../utils/rules/predicates"; +import { + ActionVariables, + visualizationDependentEvaluatorBuilder +} from "../../utils/rules/visualization-dependent-evaluator"; + +// FIXME: Update conditions and tests in EssenceProps src/common/models/essence/essence.mocha.ts +const rulesEvaluator = visualizationDependentEvaluatorBuilder + .when(Predicates.numberOfSplitsIsNot(1)) + .then(variables => Resolve.manual( + 3, + "Scatterplot needs exactly 1 split", + variables.splits.length() > 1 ? suggestRemovingSplits(variables) : suggestAddingSplits(variables) + )) + .when(Predicates.numberOfSeriesIsNot(2)) + .then(variables => Resolve.manual( + 3, + "Scatterplot needs exactly 2 measures", + variables.series.series.size < 2 ? suggestAddingMeasure(variables) : suggestRemovingMeasures(variables) + )) + .otherwise(({ isSelectedVisualization }) => + Resolve.ready(isSelectedVisualization ? 10 : 3) + ) + .build(); + +const suggestRemovingSplits = ({ splits }: ActionVariables) => [{ + description: splits.length() === 2 ? "Remove last split" : `Remove last ${splits.length() - 1} splits`, + adjustment: { splits: splits.slice(0, 1) } + }]; + +const suggestAddingSplits = ({ dataCube, splits }: ActionVariables) => + allDimensions(dataCube.dimensions) + .filter(dimension => !splits.hasSplitOn(dimension)) + .slice(0, 2) + .map(dimension => ({ + description: `Add ${dimension.title} split`, + adjustment: { + splits: splits.addSplit(Split.fromDimension(dimension)) + } + })); + +const suggestAddingMeasure = ({ dataCube, series }: ActionVariables) => { + const firstSeriesKey = series.getSeriesKeys().first(); + const notUsedMeasures = allMeasures(dataCube.measures).filter(measure => measure.name !== firstSeriesKey); + + if (notUsedMeasures.length > 0) { + const firstMeasure = notUsedMeasures[0]; + return [{ + description: `Add measure ${firstMeasure.title}`, + adjustment: { + series: series.addSeries(MeasureSeries.fromMeasure(firstMeasure)) + } + }]; + } + + return [{ + description: "Second measure needed", + adjustment: null + }]; +}; + +const suggestRemovingMeasures = ({ series }: ActionVariables) => [{ + description: series.count() === 3 ? "Remove last measure" : "Use first two measures", + adjustment: { + series: series.takeNFirst(2) + } + }]; + +export const SCATTERPLOT_MANIFEST = new VisualizationManifest( + "scatterplot", + "Scatterplot", + rulesEvaluator, + emptySettingsConfig +);