Skip to content

Commit

Permalink
Time series specific visualization controls (#986)
Browse files Browse the repository at this point in the history
* visualization controls for timeseries - only granularity on continuous split and color specific limits on category split

* use timeseries controls on linechart

* move barchart code around so we have switch between two version at top level

* simplify timeseries split tile

* adjustColorSplit should use valid available split limits

* empty commit for github workflow

* all continuous splits have the same adjustments

* handle number splits on new barchart

* fix rename in tests

* formatters for short segments

* barchart utils should handle also number ranges

* more robust number range formatter

* code style

* handle error in simulateQueryPlan for modal with Druid query
  • Loading branch information
adrianmroz-allegro authored Dec 6, 2022
1 parent d095ac8 commit fc92d6c
Show file tree
Hide file tree
Showing 19 changed files with 1,293 additions and 1,052 deletions.
114 changes: 114 additions & 0 deletions src/client/components/timeseries-visualization-controls/split-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* 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 React, { useMemo, useState } from "react";
import { colorSplitLimits } from "../../../common/models/colors/colors";
import { granularityToString } from "../../../common/models/granularity/granularity";
import { DimensionSortOn, SortOn } from "../../../common/models/sort-on/sort-on";
import { useSettingsContext } from "../../views/cube-view/settings-context";
import { getContinuousSplit } from "../../visualizations/line-chart/utils/splits";
import { GranularityPicker } from "../split-menu/granularity-picker";
import { LimitDropdown } from "../split-menu/limit-dropdown";
import { SortDropdown } from "../split-menu/sort-dropdown";
import { SplitMenuProps } from "../split-menu/split-menu";
import { createSplit, SplitMenuBase, validateSplit } from "../split-menu/split-menu-base";

const TimeSeriesContinuousSplitMenu: React.FunctionComponent<SplitMenuProps> = props => {
const { saveSplit, containerStage, split, dimension, onClose, openOn } = props;
const [granularity, setGranularity] = useState(() => split.bucket && granularityToString(split.bucket));

const onSave = () => {
const newSplit = createSplit({
dimension,
split,
granularity
});
saveSplit(split, newSplit);
};

const isValid = validateSplit({ split, dimension, granularity });

return <SplitMenuBase
openOn={openOn}
containerStage={containerStage}
onClose={onClose}
onSave={onSave}
dimension={dimension}
isValid={isValid}>
<GranularityPicker
granularity={granularity}
dimension={dimension}
granularityChange={setGranularity}/>
</SplitMenuBase>;
};

const TimeSeriesCategorySplitMenu: React.FunctionComponent<SplitMenuProps> = props => {
const { openOn, containerStage, onClose, dimension, saveSplit, split, essence } = props;

const sortOptions = [
new DimensionSortOn(dimension),
...essence.seriesSortOns(true).toArray()
];

const { customization: { visualizationColors: { series } } } = useSettingsContext();
const limitOptions = useMemo(() => colorSplitLimits(series.length), [series.length]);

const [sort, setSort] = useState(split.sort);
const [limit, setLimit] = useState(split.limit);

const isValid = validateSplit({ split, dimension, limit, sort });

const onSave = () => {
const newSplit = createSplit({
dimension,
split,
limit,
sort
});
saveSplit(split, newSplit);
};

return <SplitMenuBase
openOn={openOn}
containerStage={containerStage}
onClose={onClose}
onSave={onSave}
dimension={dimension}
isValid={isValid}>
<SortDropdown
direction={sort.direction}
selected={SortOn.fromSort(sort, essence)}
options={sortOptions}
onChange={setSort}/>
<LimitDropdown
selectedLimit={limit}
limits={limitOptions}
includeNone={false}
onLimitSelect={setLimit}/>
</SplitMenuBase>;
};

export const TimeSeriesSplitMenu: React.FunctionComponent<SplitMenuProps> = props => {
const { essence, split } = props;

const isContinuousSplit = split.equals(getContinuousSplit(essence));

if (isContinuousSplit) {
return <TimeSeriesContinuousSplitMenu {...props} />;
} else {
return <TimeSeriesCategorySplitMenu {...props} />;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 React from "react";
import { VisualizationControls, VisualizationControlsBaseProps } from "../../views/cube-view/center-panel/center-panel";
import { SplitTile, SplitTileBaseProps } from "../split-tile/split-tile";
import { SplitTilesRow, SplitTilesRowBaseProps } from "../split-tile/split-tiles-row";
import { TimeSeriesSplitMenu } from "./split-menu";

const TimeSeriesSplitTile: React.FunctionComponent<SplitTileBaseProps> = props =>
<SplitTile splitMenuComponent={TimeSeriesSplitMenu} {...props} />;

const TimeSeriesSplitTilesRow: React.FunctionComponent<SplitTilesRowBaseProps> = props =>
<SplitTilesRow {...props} splitTileComponent={TimeSeriesSplitTile}/>;

export const TimeSeriesVisualizationControls: React.FunctionComponent<VisualizationControlsBaseProps> = props =>
<VisualizationControls {...props} splitTilesRow={TimeSeriesSplitTilesRow}/>;
10 changes: 7 additions & 3 deletions src/client/modals/druid-query-modal/druid-query-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@ export const DruidQueryModal: React.FunctionComponent<DruidQueryModalProps> = ({
const queryFn = visualization.name === "grid" ? gridQuery : standardQuery;
const query = queryFn(essence, timekeeper);
const external = External.fromJS({ engine: "druid", attributes, source, customAggregations, customTransforms });
const plan = query.simulateQueryPlan({ main: external });
const planSource = JSON.stringify(plan, null, 2);
let plan;
try {
plan = JSON.stringify(query.simulateQueryPlan({ main: external }), null, 2);
} catch (e) {
plan = "Couldn't create Druid Query Plan.";
}

return <SourceModal
onClose={onClose}
title="Druid query"
copyLabel="Copy query"
source={planSource} />;
source={plan} />;
};
Loading

0 comments on commit fc92d6c

Please sign in to comment.