diff --git a/src/client/components/tabular-scroller/measures/measure-row.tsx b/src/client/components/tabular-scroller/measures/measure-row.tsx index 5ba63c9cd..70f170fcb 100644 --- a/src/client/components/tabular-scroller/measures/measure-row.tsx +++ b/src/client/components/tabular-scroller/measures/measure-row.tsx @@ -15,6 +15,7 @@ */ import * as d3 from "d3"; +import { Map } from "immutable"; import { Datum } from "plywood"; import * as React from "react"; import { Essence } from "../../../../common/models/essence/essence"; @@ -29,11 +30,12 @@ interface MeasureRowProps { style: React.CSSProperties; datum: Datum; cellWidth: number; - scales: Array>; + scales: Map>; + showBar: boolean; } export const MeasureRow: React.SFC = props => { - const { datum, scales, cellWidth, highlight, dimmed, style, essence } = props; + const { datum, showBar, scales, cellWidth, highlight, dimmed, style, essence } = props; const concreteSeries = essence.getConcreteSeries().toArray(); @@ -41,13 +43,13 @@ export const MeasureRow: React.SFC = props => { className={classNames("measure-row", { highlight, dimmed })} style={style} > - {concreteSeries.map((series, i) => { + {concreteSeries.map(series => { return ; })} diff --git a/src/client/components/tabular-scroller/measures/measure-rows.tsx b/src/client/components/tabular-scroller/measures/measure-rows.tsx index 355d231d9..aa64a2091 100644 --- a/src/client/components/tabular-scroller/measures/measure-rows.tsx +++ b/src/client/components/tabular-scroller/measures/measure-rows.tsx @@ -15,9 +15,11 @@ */ import * as d3 from "d3"; +import { Map } from "immutable"; import { Datum, PseudoDatum } from "plywood"; import * as React from "react"; import { Essence } from "../../../../common/models/essence/essence"; +import { Predicate } from "../../../../common/utils/functional/functional"; import { VisibleRows } from "../visible-rows/visible-rows"; import { MeasureRow } from "./measure-row"; @@ -25,17 +27,16 @@ interface MeasureRowsProps { visibleRowsIndexRange: [number, number]; essence: Essence; highlightedRowIndex: number | null; - scales: Array>; + scales: Map>; data: PseudoDatum[]; hoverRow?: Datum; cellWidth: number; rowWidth: number; + showBarPredicate: Predicate; } export const MeasureRows: React.SFC = props => { - const { rowWidth, essence, cellWidth, hoverRow, scales, data, visibleRowsIndexRange, highlightedRowIndex } = props; - - const lastLevel = essence.splits.length(); + const { rowWidth, showBarPredicate, essence, cellWidth, hoverRow, scales, data, visibleRowsIndexRange, highlightedRowIndex } = props; return = props => { renderRow={props => { const { index, top, datum, highlight, dimmed } = props; const rowStyle: React.CSSProperties = { top, width: rowWidth }; - const showScales = datum["__nest"] === lastLevel; + const showBar = showBarPredicate(datum); return = props => { style={rowStyle} datum={datum} cellWidth={cellWidth} - scales={showScales && scales} />; + showBar={showBar} + scales={scales} />; }} />; }; diff --git a/src/client/components/tabular-scroller/measures/measure-value.tsx b/src/client/components/tabular-scroller/measures/measure-value.tsx index 4a631fa6e..6e70915b4 100644 --- a/src/client/components/tabular-scroller/measures/measure-value.tsx +++ b/src/client/components/tabular-scroller/measures/measure-value.tsx @@ -25,14 +25,14 @@ import { MeasureCell } from "./measure-cell"; interface MeasureValueProps { series: ConcreteSeries; datum: Datum; - scale: d3.scale.Linear; + barScale?: d3.scale.Linear; cellWidth: number; showPrevious: boolean; highlight: boolean; } export const MeasureValue: React.SFC = props => { - const { series, datum, scale, highlight, showPrevious, cellWidth } = props; + const { series, datum, barScale, highlight, showPrevious, cellWidth } = props; const currentValue = series.selectValue(datum); @@ -41,7 +41,7 @@ export const MeasureValue: React.SFC = props => { width={cellWidth} value={series.formatValue(datum)} > - {scale && } + {barScale && } ; if (!showPrevious) { @@ -56,7 +56,7 @@ export const MeasureValue: React.SFC = props => { key={series.reactKey(SeriesDerivation.PREVIOUS)} width={cellWidth} value={series.formatValue(datum, SeriesDerivation.PREVIOUS)}> - {scale && } + {barScale && } ; } export const FlattenedSplits: React.SFC = props => { - const { essence, data, highlightedRowIndex, hoverRow, visibleRowsIndexRange, segmentWidth } = props; - const { splits: { splits }, timezone } = essence; + const { splitLabel: SplitLabel, data, highlightedRowIndex, hoverRow, visibleRowsIndexRange, segmentWidth } = props; return
= props => { style={segmentStyle} dimmed={dimmed} highlight={highlight}> - + ; }} />
; diff --git a/src/client/visualizations/table/utils/get-scales-for-columns.mocha.ts b/src/client/components/tabular-scroller/utils/get-scales-for-columns.mocha.ts similarity index 66% rename from src/client/visualizations/table/utils/get-scales-for-columns.mocha.ts rename to src/client/components/tabular-scroller/utils/get-scales-for-columns.mocha.ts index 17072aeb4..2fdc78bd7 100644 --- a/src/client/visualizations/table/utils/get-scales-for-columns.mocha.ts +++ b/src/client/components/tabular-scroller/utils/get-scales-for-columns.mocha.ts @@ -28,18 +28,22 @@ const essenceFixture = EssenceFixtures stringSplitCombine("channel", { sort: { reference: "delta", direction: SortDirection.descending }, limit: 50 }) ), VisStrategy.KeepAlways); +const deltaSeries = essenceFixture.getConcreteSeries().get(0); +const countSeries = essenceFixture.getConcreteSeries().get(1); +const addedSeries = essenceFixture.getConcreteSeries().get(2); + const dataMock = [ - { __nest: 1, delta: -20, count: 10000, added: -12 }, - { __nest: 1, delta: -10, count: 1312, added: 61 }, - { __nest: 1, delta: 0, count: 2312, added: Infinity }, - { __nest: 1, delta: 10, count: 9231, added: -Infinity }, - { __nest: 1, delta: 20, count: 100, added: NaN } + { delta: -20, count: 10000, added: -12 }, + { delta: -10, count: 1312, added: 61 }, + { delta: 0, count: 2312, added: Infinity }, + { delta: 10, count: 9231, added: -Infinity }, + { delta: 20, count: 100, added: NaN } ]; describe("getScalesForColumns", () => { it("should return scale for each series", () => { const scales = getScalesForColumns(essenceFixture, dataMock); - expect(scales).to.have.length(3); + expect(scales.count()).to.be.equal(3); }); it("should return scales with range [0, 100]", () => { @@ -51,27 +55,27 @@ describe("getScalesForColumns", () => { describe("delta scale", () => { it("should return scale with correct domain", () => { - const [deltaScale] = getScalesForColumns(essenceFixture, dataMock); - expect(deltaScale.domain()).to.be.deep.equal([-20, 20]); + const scales = getScalesForColumns(essenceFixture, dataMock); + expect(scales.get(deltaSeries.reactKey()).domain()).to.be.deep.equal([-20, 20]); }); }); describe("count scale", () => { it("should return scale with included 0 in domain", () => { - const [, countScale] = getScalesForColumns(essenceFixture, dataMock); - expect(countScale.domain()).to.be.deep.equal([0, 10000]); + const scales = getScalesForColumns(essenceFixture, dataMock); + expect(scales.get(countSeries.reactKey()).domain()).to.be.deep.equal([0, 10000]); }); }); describe("added scale", () => { it("should handle non numeric values", () => { - const [, , addedScale] = getScalesForColumns(essenceFixture, dataMock); - expect(addedScale.domain()).to.be.deep.equal([-Infinity, Infinity]); + const scales = getScalesForColumns(essenceFixture, dataMock); + expect(scales.get(addedSeries.reactKey()).domain()).to.be.deep.equal([-Infinity, Infinity]); }); }); it("should handle missing values", () => { - const [deltaScale] = getScalesForColumns(essenceFixture, []); - expect(deltaScale.domain()).to.be.deep.equal([0, 0]); + const scales = getScalesForColumns(essenceFixture, []); + expect(scales.get(deltaSeries.reactKey()).domain()).to.be.deep.equal([0, 0]); }); }); diff --git a/src/client/visualizations/table/utils/get-scales-for-columns.ts b/src/client/components/tabular-scroller/utils/get-scales-for-columns.ts similarity index 73% rename from src/client/visualizations/table/utils/get-scales-for-columns.ts rename to src/client/components/tabular-scroller/utils/get-scales-for-columns.ts index f53b0cf1c..38b2bd6aa 100644 --- a/src/client/visualizations/table/utils/get-scales-for-columns.ts +++ b/src/client/components/tabular-scroller/utils/get-scales-for-columns.ts @@ -14,17 +14,17 @@ * limitations under the License. */ import * as d3 from "d3"; +import { Map } from "immutable"; import { Datum, PseudoDatum } from "plywood"; import { Essence } from "../../../../common/models/essence/essence"; -export function getScalesForColumns(essence: Essence, flatData: PseudoDatum[]): Array> { - const concreteSeries = essence.getConcreteSeries().toArray(); - const splitLength = essence.splits.length(); - - return concreteSeries.map(series => { - const measureValues = flatData - .filter((d: Datum) => d["__nest"] === splitLength) - .map((d: Datum) => series.selectValue(d)); +export function getScalesForColumns(essence: Essence, flatData: PseudoDatum[]): Map> { + return essence.getConcreteSeries() + .groupBy(series => series.reactKey()) + .map(seriesCollection => seriesCollection.first()) + .toMap() + .map(series => { + const measureValues = flatData.map((d: Datum) => series.selectValue(d)); return d3.scale.linear() // Ensure that 0 is in there diff --git a/src/client/utils/dataset/selectors/selectors.ts b/src/client/utils/dataset/selectors/selectors.ts index 0dc047403..12449f4b0 100644 --- a/src/client/utils/dataset/selectors/selectors.ts +++ b/src/client/utils/dataset/selectors/selectors.ts @@ -24,7 +24,7 @@ export const selectMainDatum = (dataset: Dataset): Datum => export const selectSplitDataset = (datum: Datum): Dataset => datum[SPLIT] as Dataset; -const selectDatums = (dataset: Dataset): Datum[] => +export const selectDatums = (dataset: Dataset): Datum[] => dataset.data; export const selectSplitDatums: (datum: Datum) => Datum[] = diff --git a/src/client/visualizations/grid/measure-rows.tsx b/src/client/visualizations/grid/measure-rows.tsx deleted file mode 100644 index cd22da578..000000000 --- a/src/client/visualizations/grid/measure-rows.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2017-2018 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 { Datum, PseudoDatum } from "plywood"; -import * as React from "react"; -import { Essence } from "../../../common/models/essence/essence"; -import { MeasureRow } from "../../components/tabular-scroller/measures/measure-row"; -import { VisibleRows } from "../../components/tabular-scroller/visible-rows/visible-rows"; - -interface MeasureRowsProps { - visibleRowsIndexRange: [number, number]; - essence: Essence; - highlightedRowIndex: number | null; - scales: Array>; - data: PseudoDatum[]; - hoverRow?: Datum; - cellWidth: number; - rowWidth: number; -} - -export const MeasureRows: React.SFC = props => { - const { rowWidth, essence, cellWidth, hoverRow, scales, data, visibleRowsIndexRange, highlightedRowIndex } = props; - - return { - const { index, top, datum, highlight, dimmed } = props; - const rowStyle: React.CSSProperties = { top, width: rowWidth }; - - return ; - }} />; -}; diff --git a/src/client/visualizations/grid/scrolled-grid.tsx b/src/client/visualizations/grid/scrolled-grid.tsx index 9569adf27..07c6c1738 100644 --- a/src/client/visualizations/grid/scrolled-grid.tsx +++ b/src/client/visualizations/grid/scrolled-grid.tsx @@ -14,23 +14,25 @@ * limitations under the License. */ -import * as d3 from "d3"; -import { Dataset, Datum, PseudoDatum } from "plywood"; +import { Dataset } from "plywood"; import * as React from "react"; import { Essence } from "../../../common/models/essence/essence"; import { Stage } from "../../../common/models/stage/stage"; -import { Binary, Ternary, Unary } from "../../../common/utils/functional/functional"; +import { Binary, complement, Ternary, Unary } from "../../../common/utils/functional/functional"; import { Direction, ResizeHandle } from "../../components/resize-handle/resize-handle"; import { Scroller, ScrollerLayout, ScrollerPart } from "../../components/scroller/scroller"; import { HEADER_HEIGHT, ROW_HEIGHT, SEGMENT_WIDTH, SPACE_RIGHT } from "../../components/tabular-scroller/dimensions"; import { MeasuresHeader } from "../../components/tabular-scroller/header/measures/measures-header"; import { SplitColumnsHeader } from "../../components/tabular-scroller/header/splits/split-columns"; +import { MeasureRows } from "../../components/tabular-scroller/measures/measure-rows"; import { FlattenedSplits } from "../../components/tabular-scroller/splits/flattened-splits"; +import { getScalesForColumns } from "../../components/tabular-scroller/utils/get-scales-for-columns"; import { measureColumnsCount } from "../../components/tabular-scroller/utils/measure-columns-count"; import { visibleIndexRange } from "../../components/tabular-scroller/visible-rows/visible-index-range"; -import { selectFirstSplitDatums } from "../../utils/dataset/selectors/selectors"; -import { MeasureRows } from "./measure-rows"; +import { selectDatums } from "../../utils/dataset/selectors/selectors"; +import { SplitLabels } from "./split-labels"; import { mainSplit } from "./utils/main-split"; +import { isTotalDatum, NESTING_NAME } from "./utils/total-datum"; interface ScrolledGridProps { essence: Essence; @@ -45,20 +47,6 @@ interface ScrolledGridProps { scrollTop: number; } -function getScalesForColumns(essence: Essence, flatData: PseudoDatum[]): Array> { - const concreteSeries = essence.getConcreteSeries().toArray(); - - return concreteSeries.map(series => { - const measureValues = flatData - .map((d: Datum) => series.selectValue(d)); - - return d3.scale.linear() - // Ensure that 0 is in there - .domain(d3.extent([0, ...measureValues])) - .range([0, 100]); - }); -} - export const ScrolledGrid: React.SFC = props => { const { essence, @@ -73,7 +61,11 @@ export const ScrolledGrid: React.SFC = props => { availableWidth } = props; - const datums = selectFirstSplitDatums(data); + const datums = selectDatums(data.flatten({ + order: "preorder", + nestingName: NESTING_NAME + })); + const scales = getScalesForColumns(essence, datums.filter(complement(isTotalDatum))); const rowsCount = datums.length; const visibleRowsRange = visibleIndexRange(rowsCount, stage.height, scrollTop); const columnsCount = measureColumnsCount(essence); @@ -111,22 +103,27 @@ export const ScrolledGrid: React.SFC = props => { leftGutter={ + } segmentWidth={segmentWidth} highlightedRowIndex={null}/>} topLeftCorner={} + splits={splits}/>} body={datums && } /> diff --git a/src/client/visualizations/grid/split-labels.tsx b/src/client/visualizations/grid/split-labels.tsx new file mode 100644 index 000000000..e982dd35a --- /dev/null +++ b/src/client/visualizations/grid/split-labels.tsx @@ -0,0 +1,37 @@ +/* + * 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 { Timezone } from "chronoshift"; +import { List } from "immutable"; +import { Datum } from "plywood"; +import * as React from "react"; +import { Split } from "../../../common/models/split/split"; +import { FlattenedSplitColumns } from "../../components/tabular-scroller/splits/flattened-split-columns"; +import { isTotalDatum } from "./utils/total-datum"; + +interface SplitLabelsProps { + splits: List; + timezone: Timezone; + datum: Datum; +} + +export const SplitLabels: React.SFC = props => { + const { splits, datum, timezone } = props; + if (isTotalDatum(datum)) { + return
Total
; + } + return ; +}; diff --git a/src/client/visualizations/grid/utils/total-datum.ts b/src/client/visualizations/grid/utils/total-datum.ts new file mode 100644 index 000000000..f0d5523f9 --- /dev/null +++ b/src/client/visualizations/grid/utils/total-datum.ts @@ -0,0 +1,23 @@ +/* + * 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 { Datum } from "plywood"; + +export const NESTING_NAME = "__nest"; + +export function isTotalDatum(datum: Datum): boolean { + return datum[NESTING_NAME] === 0; +} diff --git a/src/client/visualizations/table/body/splits/split-rows.tsx b/src/client/visualizations/table/body/splits/split-rows.tsx index 68b03b378..4cc5c9e5e 100644 --- a/src/client/visualizations/table/body/splits/split-rows.tsx +++ b/src/client/visualizations/table/body/splits/split-rows.tsx @@ -17,6 +17,7 @@ import { Datum, PseudoDatum } from "plywood"; import * as React from "react"; import { Essence } from "../../../../../common/models/essence/essence"; +import { FlattenedSplitColumns } from "../../../../components/tabular-scroller/splits/flattened-split-columns"; import { FlattenedSplits } from "../../../../components/tabular-scroller/splits/flattened-splits"; import { NestedSplits } from "./nested-splits"; @@ -32,9 +33,13 @@ interface SplitRowsProps { export const SplitRows: React.SFC = props => { const { collapseRows, ...rest } = props; - const { data } = rest; + const { data, essence: { timezone, splits: { splits } } } = rest; if (!data) return null; return collapseRows ? - : + + } + /> : ; }; diff --git a/src/client/visualizations/table/scrolled-table/scrolled-table.tsx b/src/client/visualizations/table/scrolled-table/scrolled-table.tsx index be600be95..b8dff6066 100644 --- a/src/client/visualizations/table/scrolled-table/scrolled-table.tsx +++ b/src/client/visualizations/table/scrolled-table/scrolled-table.tsx @@ -26,6 +26,7 @@ import { Scroller, ScrollerLayout, ScrollerPart } from "../../../components/scro import { HEADER_HEIGHT, ROW_HEIGHT, SEGMENT_WIDTH, SPACE_RIGHT } from "../../../components/tabular-scroller/dimensions"; import { MeasuresHeader } from "../../../components/tabular-scroller/header/measures/measures-header"; import { MeasureRows } from "../../../components/tabular-scroller/measures/measure-rows"; +import { getScalesForColumns } from "../../../components/tabular-scroller/utils/get-scales-for-columns"; import { measureColumnsCount } from "../../../components/tabular-scroller/utils/measure-columns-count"; import { visibleIndexRange } from "../../../components/tabular-scroller/visible-rows/visible-index-range"; import { Highlight } from "../../highlight-controller/highlight"; @@ -34,7 +35,6 @@ import { SplitRows } from "../body/splits/split-rows"; import { SplitsHeader } from "../header/splits/splits-header"; import { Highlighter } from "../highlight/highlight"; import { getRowIndexForHighlight } from "../utils/get-row-index-for-highlight"; -import { getScalesForColumns } from "../utils/get-scales-for-columns"; const HIGHLIGHT_BUBBLE_V_OFFSET = -4; @@ -78,6 +78,8 @@ export const ScrolledTable: React.SFC = props => { dropHighlight, availableWidth } = props; + const splitLength = essence.splits.length(); + const scales = getScalesForColumns(essence, flatData.filter(d => d.__nest === splitLength)); const columnsCount = measureColumnsCount(essence); const rowsCount = flatData ? flatData.length : 0; const visibleRowsRange = visibleIndexRange(rowsCount, stage.height, scrollTop); @@ -96,6 +98,7 @@ export const ScrolledTable: React.SFC = props => { const highlightedRowIndex = getRowIndexForHighlight(essence, highlight, flatData); const showHighlight = highlightedRowIndex !== null && flatData; const maxSegmentWidth = availableWidth || SEGMENT_WIDTH; + const lastSplitLevel = essence.splits.length(); return = props => { body={flatData && datum.__nest === lastSplitLevel} visibleRowsIndexRange={visibleRowsRange} essence={essence} highlightedRowIndex={highlightedRowIndex} - scales={getScalesForColumns(essence, flatData)} + scales={scales} data={flatData} cellWidth={columnWidth} rowWidth={columnWidth * columnsCount}/>}