From 35593e493fdd2da98ecd9bb61742b087e9995a08 Mon Sep 17 00:00:00 2001 From: Adrian Mroz Date: Fri, 6 Jan 2023 21:55:40 +0100 Subject: [PATCH 1/6] WIP: basic marimekko --- CONTRIBUTING.md | 2 +- .../vis-selector/vis-selector-menu.tsx | 2 + src/client/icons/vis-marimekko.svg | 21 ++ src/client/views/cube-view/cube-view.tsx | 3 +- .../settings-component.ts | 2 + src/client/visualizations/index.ts | 1 + .../visualizations/marimekko/marimekko.tsx | 204 ++++++++++++++++++ src/common/models/series/series-format.ts | 2 +- .../visualization-manifest.ts | 2 +- .../heat-map/heat-map.ts | 9 +- src/common/visualization-manifests/index.ts | 2 + .../marimekko/marimekko.ts | 49 +++++ 12 files changed, 291 insertions(+), 8 deletions(-) create mode 100644 src/client/icons/vis-marimekko.svg create mode 100644 src/client/visualizations/marimekko/marimekko.tsx create mode 100644 src/common/visualization-manifests/marimekko/marimekko.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95bd4236e..0d57ca5f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,7 @@ const : React.FunctionComponent = () => { ; }; -export function Visualization(props: VisualizationProps) { +export default function Visualization(props: VisualizationProps) { return }/> diff --git a/src/client/components/vis-selector/vis-selector-menu.tsx b/src/client/components/vis-selector/vis-selector-menu.tsx index 775a154bc..cfd717694 100644 --- a/src/client/components/vis-selector/vis-selector-menu.tsx +++ b/src/client/components/vis-selector/vis-selector-menu.tsx @@ -95,6 +95,8 @@ export class VisSelectorMenu extends React.Component, void>} diff --git a/src/client/icons/vis-marimekko.svg b/src/client/icons/vis-marimekko.svg new file mode 100644 index 000000000..8b3465584 --- /dev/null +++ b/src/client/icons/vis-marimekko.svg @@ -0,0 +1,21 @@ + + + + vis-bar-chart + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/client/views/cube-view/cube-view.tsx b/src/client/views/cube-view/cube-view.tsx index e603067ef..1aca5aef3 100644 --- a/src/client/views/cube-view/cube-view.tsx +++ b/src/client/views/cube-view/cube-view.tsx @@ -509,7 +509,8 @@ export class CubeView extends React.Component { return menuStage.within({ left, right, - top: CONTROL_PANEL_HEIGHT + top: CONTROL_PANEL_HEIGHT, + bottom: 20 // TODO: from .cube-view .center-panel margins }); } diff --git a/src/client/visualization-settings/settings-component.ts b/src/client/visualization-settings/settings-component.ts index 256716c5e..4c58f019c 100644 --- a/src/client/visualization-settings/settings-component.ts +++ b/src/client/visualization-settings/settings-component.ts @@ -24,6 +24,7 @@ interface SettingsComponents { "bar-chart": null; "line-chart": typeof LineChartSettingsComponent; "heatmap": null; + "marimekko": null; "grid": null; "totals": null; "scatterplot": typeof ScatterplotSettingsComponent; @@ -33,6 +34,7 @@ const Components: SettingsComponents = { "bar-chart": null, "line-chart": LineChartSettingsComponent, "heatmap": null, + "marimekko": null, "grid": null, "totals": null, "table": TableSettingsComponent, diff --git a/src/client/visualizations/index.ts b/src/client/visualizations/index.ts index fc780a87b..40cf8881e 100644 --- a/src/client/visualizations/index.ts +++ b/src/client/visualizations/index.ts @@ -24,6 +24,7 @@ const VISUALIZATIONS = { "line-chart": () => import(/* webpackChunkName: "line-chart" */ "./line-chart/line-chart"), "bar-chart": () => import(/* webpackChunkName: "bar-chart" */ "./bar-chart/bar-chart"), "heatmap": () => import(/* webpackChunkName: "heatmap" */ "./heat-map/heat-map"), + "marimekko": () => import(/* webpackChunkName: "marimekko" */ "./marimekko/marimekko"), "grid": () => import(/* webpackChunkName: "grid" */ "./grid/grid"), "scatterplot": () => import(/* webpackChunkName: "scatterplot" */ "./scatterplot/scatterplot") }; diff --git a/src/client/visualizations/marimekko/marimekko.tsx b/src/client/visualizations/marimekko/marimekko.tsx new file mode 100644 index 000000000..f78698ffa --- /dev/null +++ b/src/client/visualizations/marimekko/marimekko.tsx @@ -0,0 +1,204 @@ +/* + * 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 d3 from "d3"; +import { sum } from "d3"; +import { Dataset, Datum } from "plywood"; +import React from "react"; +import { ChartProps } from "../../../common/models/chart-props/chart-props"; +import { findDimensionByName } from "../../../common/models/dimension/dimensions"; +import { Essence } from "../../../common/models/essence/essence"; +import { percentFormatter } from "../../../common/models/series/series-format"; +import { Stage } from "../../../common/models/stage/stage"; +import { flatMap } from "../../../common/utils/functional/functional"; +import { mapValues } from "../../../common/utils/object/object"; +import makeQuery from "../../../common/utils/query/visualization-query"; +import { LegendSpot } from "../../components/pinboard-panel/pinboard-panel"; +import { selectFirstSplitDatums, selectSplitDatums } from "../../utils/dataset/selectors/selectors"; +import { + ChartPanel, + DefaultVisualizationControls, + VisualizationProps +} from "../../views/cube-view/center-panel/center-panel"; +import { useSettingsContext } from "../../views/cube-view/settings-context"; +import { Legend } from "../line-chart/legend/legend"; // import from different viz + +function prepareData(data: Dataset, essence: Essence) { + const series = essence.getConcreteSeries().first(); + const xSplit = essence.splits.getSplit(1); + + const ySplit = essence.splits.getSplit(0); + + const dataset = selectFirstSplitDatums(data); + + const baseYs = dataset.map(datum => ySplit.selectValue(datum)); + + const xs: Record = {}; + + dataset.forEach(datum => { + const splitDatums = selectSplitDatums(datum); + const yValue = ySplit.selectValue(datum); + const y = { + [ySplit.reference]: yValue + }; + splitDatums.forEach(splitDatum => { + const x = String(xSplit.selectValue(splitDatum)); + if (xs[x] === undefined) { + xs[x] = []; + } + xs[x].push({ ...splitDatum, ...y }); + }); + }); + + const xs2 = mapValues(xs, ys => { + const x = d3.sum(ys, datum => series.selectValue(datum)); + return { + x, + ys + }; + }); + + function stackYs(ys: Datum[]): Array<{ name: string, y: number, y0: number }> { + const sorted = flatMap(baseYs, y => { + const found = ys.find(datum => ySplit.selectValue(datum) === y); + return found ? [found] : []; + }); + return sorted.map((datum, index, coll) => { + const name = String(ySplit.selectValue(datum)); + const y = series.selectValue(datum); + const y0 = sum(coll.slice(0, index), datum => series.selectValue(datum)); + + return { + name, + y, + y0 + }; + }); + } + + const xs3 = Object.entries(xs2) + .map(([name, value]) => ({ name, value })) + .sort(({ value: a }, { value: b }) => b.x - a.x) + .map(({ value, name }, index, coll) => { + const { x } = value; + const x0 = sum(coll.slice(0, index), ({ value: { x } }) => x); + const ys = stackYs(value.ys); + return { name, value: { x, x0, ys } }; + }); + + return xs3; +} + +const Marimekko: React.FunctionComponent = props => { + const { stage, data: dataset, essence } = props; + const { dataCube: { dimensions } } = essence; + const { customization } = useSettingsContext(); + const colors = customization.visualizationColors.series; + const chartStage = new Stage({ + x: 10, + y: 20, + height: stage.height - 30, + width: stage.width - 20 + }); + + const series = essence.getConcreteSeries().first(); + + const ySplit = essence.splits.getSplit(0); + const yDimension = findDimensionByName(dimensions, ySplit.reference); + const colorValues = selectFirstSplitDatums(dataset).map(datum => String(ySplit.selectValue(datum))); + + const colorScale = d3.scaleOrdinal() + .range(colors) + .domain(colorValues); + + const data = prepareData(dataset, essence); + + const total = sum(data, datum => datum.value.x); + const xScale = d3.scaleLinear() + .range([0, chartStage.width]) + .domain([0, total]); + + // TODO: magic 30! + const stackHeight = chartStage.height - 30; + + return
+ + + + + + {data.map(datum => { + const { name, value: { x, x0, ys } } = datum; + const xpx = xScale(x0); + + const yScale = d3.scaleLinear() + .range([0, stackHeight]) + .domain([0, x]); + + return + + {name}: {series.formatter()(x)} ({percentFormatter(x / total)}) + + + {ys.map(datum => { + const { name, y, y0 } = datum; + const ypx = yScale(y0); + const height = yScale(y); + + const width = xScale(x); + return + + {name}: {series.formatter()(y)} ({percentFormatter(y / x)}) + {ypx === yScale(0) ? null : } + ; + })} + {xpx === 0 ? null : + } + + ; + })} + + + +
; +}; + +export default function marimekkoVisualization(props: VisualizationProps) { + return + + + ; +} diff --git a/src/common/models/series/series-format.ts b/src/common/models/series/series-format.ts index b87d1bc72..db48065f7 100644 --- a/src/common/models/series/series-format.ts +++ b/src/common/models/series/series-format.ts @@ -53,7 +53,7 @@ export function formatFnFactory(format: string): (n: number) => string { export const exactFormat = "0,0"; const exactFormatter = formatFnFactory(exactFormat); export const percentFormat = "0[.]00%"; -const percentFormatter = formatFnFactory(percentFormat); +export const percentFormatter = formatFnFactory(percentFormat); export const measureDefaultFormat = "0,0.0 a"; export const defaultFormatter = formatFnFactory(measureDefaultFormat); diff --git a/src/common/models/visualization-manifest/visualization-manifest.ts b/src/common/models/visualization-manifest/visualization-manifest.ts index 8d7c7c3b3..c4482e3d9 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" | "scatterplot"; +export type Visualization = "heatmap" | "table" | "totals" | "bar-chart" | "line-chart" | "grid" | "scatterplot" | "marimekko"; export class VisualizationManifest { constructor( diff --git a/src/common/visualization-manifests/heat-map/heat-map.ts b/src/common/visualization-manifests/heat-map/heat-map.ts index c91ce9b49..2df822ad2 100644 --- a/src/common/visualization-manifests/heat-map/heat-map.ts +++ b/src/common/visualization-manifests/heat-map/heat-map.ts @@ -60,12 +60,12 @@ const rulesEvaluator = visualizationDependentEvaluatorBuilder }) .build(); -const suggestRemovingSplits = ({ splits }: ActionVariables) => [{ +export const suggestRemovingSplits = ({ splits }: ActionVariables) => [{ description: splits.length() === 3 ? "Remove last split" : `Remove last ${splits.length() - 2} splits`, adjustment: { splits: splits.slice(0, 2) } }]; -const suggestAddingSplits = ({ dataCube, splits }: ActionVariables) => +export const suggestAddingSplits = ({ dataCube, splits }: ActionVariables) => allDimensions(dataCube.dimensions) .filter(dimension => !splits.hasSplitOn(dimension)) .slice(0, 2) @@ -76,7 +76,7 @@ const suggestAddingSplits = ({ dataCube, splits }: ActionVariables) => } })); -const suggestAddingMeasure = ({ dataCube, series }: ActionVariables) => { +export const suggestAddingMeasure = ({ dataCube, series }: ActionVariables) => { const firstMeasure = allMeasures(dataCube.measures)[0]; return [{ description: `Add measure ${firstMeasure.title}`, @@ -86,7 +86,8 @@ const suggestAddingMeasure = ({ dataCube, series }: ActionVariables) => { }]; }; -const suggestRemovingMeasures = ({ series }: ActionVariables) => [{ +// TODO: Move these exports to commons +export const suggestRemovingMeasures = ({ series }: ActionVariables) => [{ description: series.count() === 2 ? "Remove last measure" : `Remove last ${series.count() - 1} measures`, adjustment: { series: series.takeFirst() diff --git a/src/common/visualization-manifests/index.ts b/src/common/visualization-manifests/index.ts index ee0528beb..78fb31d9f 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 { MARIMEKKO_MANIFEST } from "./marimekko/marimekko"; import { SCATTERPLOT_MANIFEST } from "./scatterplot/scatterplot"; import { TABLE_MANIFEST } from "./table/table"; import { TOTALS_MANIFEST } from "./totals/totals"; @@ -31,6 +32,7 @@ export const MANIFESTS: VisualizationManifest[] = [ LINE_CHART_MANIFEST as unknown as VisualizationManifest, BAR_CHART_MANIFEST, HEAT_MAP_MANIFEST, + MARIMEKKO_MANIFEST, TABLE_MANIFEST as unknown as VisualizationManifest, SCATTERPLOT_MANIFEST as unknown as VisualizationManifest ]; diff --git a/src/common/visualization-manifests/marimekko/marimekko.ts b/src/common/visualization-manifests/marimekko/marimekko.ts new file mode 100644 index 000000000..01c5c49ad --- /dev/null +++ b/src/common/visualization-manifests/marimekko/marimekko.ts @@ -0,0 +1,49 @@ +/* + * 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 { Resolve, VisualizationManifest } from "../../models/visualization-manifest/visualization-manifest"; +import { emptySettingsConfig } from "../../models/visualization-settings/empty-settings-config"; +import { Predicates } from "../../utils/rules/predicates"; +import { visualizationDependentEvaluatorBuilder } from "../../utils/rules/visualization-dependent-evaluator"; +import { + suggestAddingMeasure, + suggestAddingSplits, + suggestRemovingMeasures, + suggestRemovingSplits +} from "../heat-map/heat-map"; + +const rulesEvaluator = visualizationDependentEvaluatorBuilder + .when(Predicates.numberOfSplitsIsNot(2)) + .then(variables => Resolve.manual( + 3, + "Marimekko needs exactly 2 splits", + variables.splits.length() > 2 ? suggestRemovingSplits(variables) : suggestAddingSplits(variables) + )) + .when(Predicates.numberOfSeriesIsNot(1)) + .then(variables => Resolve.manual( + 3, + "Marimekko needs exactly 1 measure", + variables.series.series.size === 0 ? suggestAddingMeasure(variables) : suggestRemovingMeasures(variables) + )) + .otherwise(({ isSelectedVisualization }) => Resolve.ready(isSelectedVisualization ? 10 : 3)) + .build(); + +export const MARIMEKKO_MANIFEST = new VisualizationManifest( + "marimekko", + "Marimekko", + rulesEvaluator, + emptySettingsConfig +); From 403557e12c510b76d7ff094e4ce5f6bfa58bad4f Mon Sep 17 00:00:00 2001 From: Adrian Mroz Date: Fri, 6 Jan 2023 22:22:39 +0100 Subject: [PATCH 2/6] Due to changes in cube-view around chartStage. Maybe we should change here or maybe change size in e2e test --- .../visualizations/scatterplot/utils/get-plotting-data.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/visualizations/scatterplot/utils/get-plotting-data.ts b/src/client/visualizations/scatterplot/utils/get-plotting-data.ts index 7de3e257b..bcd180656 100644 --- a/src/client/visualizations/scatterplot/utils/get-plotting-data.ts +++ b/src/client/visualizations/scatterplot/utils/get-plotting-data.ts @@ -24,7 +24,7 @@ import { LinearScale } from "../../../utils/linear-scale/linear-scale"; const MARGIN = 40; const X_AXIS_HEIGHT = 50; const Y_AXIS_WIDTH = 50; -const BREAKPOINT_SMALL = 768; +const BREAKPOINT_SMALL = 748; const TICK_COUNT = 10; const EXTENT_EXTEND_FACTOR = 0.05; const X_AXIS_LABEL_OFFSET = 55; From 8941b99d271e2d721abf35d6a6cbf23e9a62f11a Mon Sep 17 00:00:00 2001 From: Adrian Mroz Date: Sun, 8 Jan 2023 18:07:48 +0100 Subject: [PATCH 3/6] WIP: use plywoodKeys instead of generic variable names --- .../visualizations/marimekko/marimekko.tsx | 61 ++++++++++++------- src/common/utils/object/object.ts | 6 +- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/client/visualizations/marimekko/marimekko.tsx b/src/client/visualizations/marimekko/marimekko.tsx index f78698ffa..8c9bebfb5 100644 --- a/src/client/visualizations/marimekko/marimekko.tsx +++ b/src/client/visualizations/marimekko/marimekko.tsx @@ -21,6 +21,7 @@ import React from "react"; import { ChartProps } from "../../../common/models/chart-props/chart-props"; import { findDimensionByName } from "../../../common/models/dimension/dimensions"; import { Essence } from "../../../common/models/essence/essence"; +import { ConcreteSeries } from "../../../common/models/series/concrete-series"; // import from different viz import { percentFormatter } from "../../../common/models/series/series-format"; import { Stage } from "../../../common/models/stage/stage"; import { flatMap } from "../../../common/utils/functional/functional"; @@ -34,7 +35,7 @@ import { VisualizationProps } from "../../views/cube-view/center-panel/center-panel"; import { useSettingsContext } from "../../views/cube-view/settings-context"; -import { Legend } from "../line-chart/legend/legend"; // import from different viz +import { Legend } from "../line-chart/legend/legend"; function prepareData(data: Dataset, essence: Essence) { const series = essence.getConcreteSeries().first(); @@ -63,45 +64,54 @@ function prepareData(data: Dataset, essence: Essence) { }); }); - const xs2 = mapValues(xs, ys => { - const x = d3.sum(ys, datum => series.selectValue(datum)); + const xs2 = mapValues(xs, (data, key): Datum => { + const measure = { + [series.plywoodKey()]: d3.sum(data, datum => series.selectValue(datum)) + }; + const x = { + [xSplit.toKey()]: key + }; return { - x, - ys + ...x, + ...measure, + nest: Dataset.fromJS(data) }; }); - function stackYs(ys: Datum[]): Array<{ name: string, y: number, y0: number }> { + function stackYs(ys: Datum[]): Datum[] { const sorted = flatMap(baseYs, y => { const found = ys.find(datum => ySplit.selectValue(datum) === y); return found ? [found] : []; }); return sorted.map((datum, index, coll) => { - const name = String(ySplit.selectValue(datum)); - const y = series.selectValue(datum); const y0 = sum(coll.slice(0, index), datum => series.selectValue(datum)); return { - name, - y, - y0 + ...datum, + [x0Key(series)]: y0 }; }); } - const xs3 = Object.entries(xs2) - .map(([name, value]) => ({ name, value })) - .sort(({ value: a }, { value: b }) => b.x - a.x) - .map(({ value, name }, index, coll) => { - const { x } = value; - const x0 = sum(coll.slice(0, index), ({ value: { x } }) => x); - const ys = stackYs(value.ys); - return { name, value: { x, x0, ys } }; + const xs3 = Object.values(xs2) + .sort((a, b) => series.selectValue(b) - series.selectValue(a)) + .map((datum, index, coll) => { + const x0 = sum(coll.slice(0, index), d => series.selectValue(d)); + const stackedNest = stackYs((datum.nest as Dataset).data); + return { + ...datum, + [x0Key(series)]: x0, + nest: Dataset.fromJS(stackedNest) + }; }); + console.log(xs3); + return xs3; } +const x0Key = (series: ConcreteSeries) => `__${series.plywoodKey()}_0`; + const Marimekko: React.FunctionComponent = props => { const { stage, data: dataset, essence } = props; const { dataCube: { dimensions } } = essence; @@ -117,6 +127,7 @@ const Marimekko: React.FunctionComponent = props => { const series = essence.getConcreteSeries().first(); const ySplit = essence.splits.getSplit(0); + const xSplit = essence.splits.getSplit(1); const yDimension = findDimensionByName(dimensions, ySplit.reference); const colorValues = selectFirstSplitDatums(dataset).map(datum => String(ySplit.selectValue(datum))); @@ -126,7 +137,7 @@ const Marimekko: React.FunctionComponent = props => { const data = prepareData(dataset, essence); - const total = sum(data, datum => datum.value.x); + const total = sum(data, datum => series.selectValue(datum)); const xScale = d3.scaleLinear() .range([0, chartStage.width]) .domain([0, total]); @@ -141,8 +152,11 @@ const Marimekko: React.FunctionComponent = props => { {data.map(datum => { - const { name, value: { x, x0, ys } } = datum; + const x = series.selectValue(datum); + const x0 = datum[x0Key(series)] as number; + const name = String(xSplit.selectValue(datum)); const xpx = xScale(x0); + const ys = (datum.nest as Dataset).data; const yScale = d3.scaleLinear() .range([0, stackHeight]) @@ -154,7 +168,10 @@ const Marimekko: React.FunctionComponent = props => { {ys.map(datum => { - const { name, y, y0 } = datum; + const y = series.selectValue(datum); + const y0 = datum[x0Key(series)] as number; + const name = String(ySplit.selectValue(datum)); + const ypx = yScale(y0); const height = yScale(y); diff --git a/src/common/utils/object/object.ts b/src/common/utils/object/object.ts index beb1194a1..d194c0a3a 100644 --- a/src/common/utils/object/object.ts +++ b/src/common/utils/object/object.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { assoc, Unary } from "../functional/functional"; +import { assoc, Binary, Unary } from "../functional/functional"; import { isTruthy } from "../general/general"; export function extend(source: any, target: any): any { @@ -37,9 +37,9 @@ export function omitFalsyValues(obj: T): Partial { type Key = string; -export function mapValues(obj: Record, fn: Unary): Record { +export function mapValues(obj: Record, fn: Binary): Record { return Object.keys(obj).reduce((result: Record, key: K) => { - result[key] = fn(obj[key]); + result[key] = fn(obj[key], key); return result; }, {} as Record); } From 98cc98b571f997b37ac79940bf81cd4c42cbe7d5 Mon Sep 17 00:00:00 2001 From: Adrian Mroz Date: Sun, 8 Jan 2023 19:09:35 +0100 Subject: [PATCH 4/6] WIP: divs everywhere! --- .../visualizations/marimekko/marimekko.scss | 28 +++++ .../visualizations/marimekko/marimekko.tsx | 118 ++++++++---------- 2 files changed, 79 insertions(+), 67 deletions(-) create mode 100644 src/client/visualizations/marimekko/marimekko.scss diff --git a/src/client/visualizations/marimekko/marimekko.scss b/src/client/visualizations/marimekko/marimekko.scss new file mode 100644 index 000000000..5af1b0ecf --- /dev/null +++ b/src/client/visualizations/marimekko/marimekko.scss @@ -0,0 +1,28 @@ +/* + * 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 '../../imports'; + +.marimekko .absolute { + @include pin-full; +} + +.marimekko .rect { + @include ellipsis; + color: white; + padding: 8px; + border: 2px solid white; +} diff --git a/src/client/visualizations/marimekko/marimekko.tsx b/src/client/visualizations/marimekko/marimekko.tsx index 8c9bebfb5..355e713aa 100644 --- a/src/client/visualizations/marimekko/marimekko.tsx +++ b/src/client/visualizations/marimekko/marimekko.tsx @@ -17,11 +17,10 @@ import * as d3 from "d3"; import { sum } from "d3"; import { Dataset, Datum } from "plywood"; -import React from "react"; +import React, { CSSProperties } from "react"; import { ChartProps } from "../../../common/models/chart-props/chart-props"; import { findDimensionByName } from "../../../common/models/dimension/dimensions"; import { Essence } from "../../../common/models/essence/essence"; -import { ConcreteSeries } from "../../../common/models/series/concrete-series"; // import from different viz import { percentFormatter } from "../../../common/models/series/series-format"; import { Stage } from "../../../common/models/stage/stage"; import { flatMap } from "../../../common/utils/functional/functional"; @@ -36,6 +35,7 @@ import { } from "../../views/cube-view/center-panel/center-panel"; import { useSettingsContext } from "../../views/cube-view/settings-context"; import { Legend } from "../line-chart/legend/legend"; +import "./marimekko.scss"; function prepareData(data: Dataset, essence: Essence) { const series = essence.getConcreteSeries().first(); @@ -50,12 +50,12 @@ function prepareData(data: Dataset, essence: Essence) { const xs: Record = {}; dataset.forEach(datum => { - const splitDatums = selectSplitDatums(datum); + const nested = selectSplitDatums(datum); const yValue = ySplit.selectValue(datum); const y = { [ySplit.reference]: yValue }; - splitDatums.forEach(splitDatum => { + nested.forEach(splitDatum => { const x = String(xSplit.selectValue(splitDatum)); if (xs[x] === undefined) { xs[x] = []; @@ -88,7 +88,7 @@ function prepareData(data: Dataset, essence: Essence) { return { ...datum, - [x0Key(series)]: y0 + y0 }; }); } @@ -100,17 +100,15 @@ function prepareData(data: Dataset, essence: Essence) { const stackedNest = stackYs((datum.nest as Dataset).data); return { ...datum, - [x0Key(series)]: x0, + x0, nest: Dataset.fromJS(stackedNest) }; }); - console.log(xs3); - return xs3; } -const x0Key = (series: ConcreteSeries) => `__${series.plywoodKey()}_0`; +const X_AXIS_HEIGHT = 30; const Marimekko: React.FunctionComponent = props => { const { stage, data: dataset, essence } = props; @@ -142,75 +140,61 @@ const Marimekko: React.FunctionComponent = props => { .range([0, chartStage.width]) .domain([0, total]); - // TODO: magic 30! - const stackHeight = chartStage.height - 30; + const stackHeight = chartStage.height - X_AXIS_HEIGHT; - return
+ return <> - + - - - {data.map(datum => { - const x = series.selectValue(datum); - const x0 = datum[x0Key(series)] as number; - const name = String(xSplit.selectValue(datum)); - const xpx = xScale(x0); - const ys = (datum.nest as Dataset).data; - - const yScale = d3.scaleLinear() - .range([0, stackHeight]) - .domain([0, x]); - - return - +
+ {data.map(datum => { + const x = series.selectValue(datum); + const x0 = datum.x0 as number; + const name = String(xSplit.selectValue(datum)); + const left = xScale(x0); + const width = xScale(x); + const ys = (datum.nest as Dataset).data; + + const yScale = d3.scaleLinear() + .range([0, stackHeight]) + .domain([0, x]); + + return ( +
+ {name}: {series.formatter()(x)} ({percentFormatter(x / total)}) - - + +
{ys.map(datum => { const y = series.selectValue(datum); - const y0 = datum[x0Key(series)] as number; + const y0 = datum.y0 as number; const name = String(ySplit.selectValue(datum)); - const ypx = yScale(y0); + const top = yScale(y0); const height = yScale(y); - const width = xScale(x); - return - - {name}: {series.formatter()(y)} ({percentFormatter(y / x)}) - {ypx === yScale(0) ? null : } - ; + + const styles: CSSProperties = { + top, + height, + width, + backgroundColor: colorScale(name) + }; + + return
+ {name}: {series.formatter()(y)} ({percentFormatter(y / x)}) +
; })} - {xpx === 0 ? null : - } - - ; - })} - - - -
; +
+
+ ); + })} +
+ ; }; export default function marimekkoVisualization(props: VisualizationProps) { From 4205505897ef37081dfef6e4aedcd12e18a19a3c Mon Sep 17 00:00:00 2001 From: Adrian Mroz Date: Sun, 8 Jan 2023 19:13:39 +0100 Subject: [PATCH 5/6] WIP: adaptive text color --- src/client/visualizations/marimekko/marimekko.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/client/visualizations/marimekko/marimekko.tsx b/src/client/visualizations/marimekko/marimekko.tsx index 355e713aa..b50aad7d0 100644 --- a/src/client/visualizations/marimekko/marimekko.tsx +++ b/src/client/visualizations/marimekko/marimekko.tsx @@ -174,11 +174,15 @@ const Marimekko: React.FunctionComponent = props => { const height = yScale(y); const width = xScale(x); + const backgroundColor = colorScale(name); + const textColor = d3.hsl(backgroundColor).l > 0.5 ? "#000" : "#fff"; + const styles: CSSProperties = { top, height, width, - backgroundColor: colorScale(name) + backgroundColor, + color: textColor }; return
Date: Sun, 8 Jan 2023 19:15:38 +0100 Subject: [PATCH 6/6] WIP: lint fixes --- src/client/visualizations/marimekko/marimekko.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/visualizations/marimekko/marimekko.scss b/src/client/visualizations/marimekko/marimekko.scss index 5af1b0ecf..22baaf051 100644 --- a/src/client/visualizations/marimekko/marimekko.scss +++ b/src/client/visualizations/marimekko/marimekko.scss @@ -22,7 +22,6 @@ .marimekko .rect { @include ellipsis; - color: white; padding: 8px; - border: 2px solid white; + border: 2px solid #fff; }