Commit e1339bf1 authored by Jakub Beránek's avatar Jakub Beránek
Browse files

ENH: enable users to select sort mode for charts

parent ad723d04
......@@ -121,6 +121,7 @@ class BarChartPageComponent extends PureComponent<Props, State>
width={1000}
height={400}
fitToDomain={settings.fitToDomain}
sortMode={this.props.xAxisSettings.sortMode}
onMeasurementsSelected={this.changeSelectedMeasurements} />}
</ChartToolbarWrapper>
);
......
......@@ -18,6 +18,7 @@ import {Measurement} from '../../../../lib/measurement/measurement';
import {formatKey} from '../../../../util/measurement';
import {ColorPalette} from '../../color-palette';
import {formatYAxis, groupMeasurements, linearizeGroups, MeasurementGroup} from '../chart-utils';
import {SortMode} from '../sort-mode';
import {Tick} from '../tick';
import {BarTooltip} from './bar-tooltip';
......@@ -33,6 +34,7 @@ interface Props
width?: number;
height: number;
fitToDomain?: boolean;
sortMode: SortMode;
onMeasurementsSelected(measurements: Measurement[]): void;
}
......@@ -42,10 +44,11 @@ export class BarChart extends PureComponent<Props>
{
private groups = memoizeOne(
(measurements: Measurement[], groupMode: GroupMode, xAxis: string,
yAxes: string[], dateFormat: string) =>
yAxes: string[], dateFormat: string, sortMode: SortMode) =>
linearizeGroups(
groupMeasurements(measurements, groupMode, xAxis, yAxes, dateFormat),
dateFormat
dateFormat,
sortMode
)
);
......@@ -66,7 +69,7 @@ export class BarChart extends PureComponent<Props>
{
const yAxes = this.props.yAxes;
let data = this.groups(this.props.measurements, this.props.groupMode,
this.props.xAxis, yAxes, this.props.dateFormat);
this.props.xAxis, yAxes, this.props.dateFormat, this.props.sortMode);
const empty = data.length === 0;
if (empty)
......
......@@ -5,6 +5,7 @@ import {hashMeasurement, Measurement} from '../../../lib/measurement/measurement
import {compareDate} from '../../../util/date';
import {standardDeviation} from '../../../util/math';
import {getValueWithPath} from '../../../util/object';
import {SortMode} from './sort-mode';
export interface Deviation
......@@ -57,10 +58,11 @@ export function groupMeasurements(measurements: Measurement[],
const groups: Dictionary<MeasurementGroup> = map(batch => createGroup(batch, axisX, axesY), batches);
return filter(isGroupValid, groups);
}
export function linearizeGroups(groups: Dictionary<MeasurementGroup>, dateFormat: string): MeasurementGroup[]
export function linearizeGroups(groups: Dictionary<MeasurementGroup>, dateFormat: string, sortMode: SortMode)
: MeasurementGroup[]
{
return sort(
(a, b) => compareDate(getMinTimestamp(a), getMinTimestamp(b)),
(a, b) => sortPoints(sortMode, getMinTimestamp, a, b),
values(groups)
).map(v => transformDateAxis(v, dateFormat));
}
......@@ -123,6 +125,24 @@ export function formatYAxis(value: string): string
return value;
}
export function sortPoints<T extends {x: string}>(sortMode: SortMode,
getTimestamp: (t: T) => Moment, a: T, b: T): number
{
if (sortMode === SortMode.Timestamp)
{
return compareDate(getTimestamp(a), getTimestamp(b));
}
else if (sortMode === SortMode.AxisXNumeric)
{
return Number(a.x) - Number(b.x);
}
else if (sortMode === SortMode.AxisXLexicographic)
{
return a.x.toLocaleLowerCase().localeCompare(b.x.toLocaleLowerCase());
}
else throw new Error(`Invalid sort mode: ${sortMode}`);
}
function hasAxis(measurement: Measurement, axis: string)
{
return getValueWithPath(measurement, axis) !== undefined;
......
......@@ -150,6 +150,7 @@ class GridChartPageComponent extends PureComponent<Props, State>
height={150}
preview={true}
groupMode={GroupMode.AxisX}
sortMode={this.props.xAxisSettings.sortMode}
settings={this.settings}
dateFormat={this.props.xAxisSettings.dateFormat} />
</Dataset>
......
......@@ -124,6 +124,7 @@ class LineChartPageComponent extends PureComponent<Props, State>
onMeasurementsSelected={this.changeSelectedMeasurements}
datasets={datasets}
dateFormat={this.props.xAxisSettings.dateFormat}
sortMode={this.props.xAxisSettings.sortMode}
fitToDomain={settings.fitToDomain}
chartRef={ref} />
}
......
......@@ -18,6 +18,7 @@ import {GroupMode} from '../../../../lib/measurement/group-mode';
import {Measurement} from '../../../../lib/measurement/measurement';
import {ColorPalette} from '../../color-palette';
import {formatYAxis} from '../chart-utils';
import {SortMode} from '../sort-mode';
import {Tick} from '../tick';
import {LineChartSettings} from './line-chart-settings';
import {LineLegend} from './line-legend';
......@@ -45,6 +46,7 @@ interface Props
settings: LineChartSettings;
preview?: boolean;
dateFormat: string;
sortMode: SortMode;
chartRef?: RefObject<ReLineChart>;
onMeasurementsSelected?(measurements: Measurement[]): void;
}
......@@ -55,9 +57,9 @@ export class LineChart extends PureComponent<Props>
{
private lineData = memoizeOne(
(datasets: LineChartDataset[], groupMode: GroupMode, xAxis: string,
dateFormat: string, showAverageTrend: boolean) =>
dateFormat: string, showAverageTrend: boolean, sortMode: SortMode) =>
createLineData(datasets, groupMode, xAxis,
dateFormat, showAverageTrend, DATASET_COLORS
dateFormat, showAverageTrend, sortMode, DATASET_COLORS
)
);
......@@ -85,7 +87,7 @@ export class LineChart extends PureComponent<Props>
const padding = preview ? 10 : 20;
let {points, groups} = this.lineData(this.props.datasets, this.props.groupMode, this.props.xAxis,
this.props.dateFormat, this.props.settings.showAverageTrend);
this.props.dateFormat, this.props.settings.showAverageTrend, this.props.sortMode);
const empty = points.length === 0;
if (empty)
......
......@@ -4,7 +4,8 @@ import {GroupMode} from '../../../../lib/measurement/group-mode';
import {compareDate} from '../../../../util/date';
import {exponentialAverage} from '../../../../util/math';
import {ColorPalette} from '../../color-palette';
import {formatXValue, getMinTimestamp, groupMeasurements, linearizeGroups} from '../chart-utils';
import {formatXValue, getMinTimestamp, groupMeasurements, linearizeGroups, sortPoints} from '../chart-utils';
import {SortMode} from '../sort-mode';
import {LineChartDataset} from './line-chart';
import {LabeledGroup, LinePoint} from './line-point';
......@@ -13,6 +14,7 @@ export function createLineData(datasets: LineChartDataset[],
xAxis: string,
dateFormat: string,
useAverages: boolean,
sortMode: SortMode,
palette: ColorPalette): {
groups: LabeledGroup[],
points: LinePoint[]
......@@ -29,7 +31,7 @@ export function createLineData(datasets: LineChartDataset[],
{
groups.push(...groups.map((labeledGroup, i) => {
const group = labeledGroup.group;
const linearized = linearizeGroups(group, dateFormat);
const linearized = linearizeGroups(group, dateFormat, sortMode);
const averageGroup = clone(group);
linearized.forEach((l, index) => {
const key = Object.keys(group[l.x].items)[0];
......@@ -49,11 +51,11 @@ export function createLineData(datasets: LineChartDataset[],
return {
groups,
points: createLinePoints(groups, dateFormat)
points: createLinePoints(groups, dateFormat, sortMode)
};
}
function createLinePoints(datasets: LabeledGroup[], dateFormat: string): LinePoint[]
function createLinePoints(datasets: LabeledGroup[], dateFormat: string, sortMode: SortMode): LinePoint[]
{
const keys = uniq(chain(d => Object.keys(d.group), datasets));
const vals: LinePoint[] = keys.map(x => ({
......@@ -81,7 +83,7 @@ function createLinePoints(datasets: LabeledGroup[], dateFormat: string): LinePoi
})
}));
return sort((a, b) => compareDate(getPointTimestamp(a), getPointTimestamp(b)), vals).map(val => ({
return sort((a, b) => sortPoints(sortMode, getPointTimestamp, a, b), vals).map(val => ({
...val,
x: formatXValue(val.x, dateFormat)
}));
......
export enum SortMode
{
Timestamp,
AxisXNumeric,
AxisXLexicographic
}
......@@ -3,6 +3,7 @@ import {Input} from 'reactstrap';
import {Project} from '../../../lib/project/project';
import {MeasurementKeys} from '../../global/keys/measurement-keys';
import {DATE_FORMAT_DAY, DATE_FORMAT_HOUR, DATE_FORMAT_MONTH, DateFormat} from './date-format';
import {SortMode} from './sort-mode';
import {XAxisSettings} from './x-axis-settings';
interface Props
......@@ -21,22 +22,42 @@ export class XAxisSelector extends PureComponent<Props>
<MeasurementKeys project={this.props.project}
value={this.props.settings.xAxis}
onChange={this.changeXAxis} />
{this.props.settings.xAxis === 'timestamp' &&
<div>
Group by:
<Input type='select'
bsSize='sm'
value={this.props.settings.dateFormat}
onChange={this.changeDateFormat}>
<option value={DATE_FORMAT_MONTH}>Month</option>
<option value={DATE_FORMAT_DAY}>Day</option>
<option value={DATE_FORMAT_HOUR}>Hour</option>
</Input>
</div>
}
{this.props.settings.xAxis === 'timestamp' ? this.renderTimestampGranularity() : this.renderSortMode()}
</>
);
}
renderTimestampGranularity = (): JSX.Element =>
{
return (
<div>
Group by:
<Input type='select'
bsSize='sm'
value={this.props.settings.dateFormat}
onChange={this.changeDateFormat}>
<option value={DATE_FORMAT_MONTH}>Month</option>
<option value={DATE_FORMAT_DAY}>Day</option>
<option value={DATE_FORMAT_HOUR}>Hour</option>
</Input>
</div>
);
}
renderSortMode = (): JSX.Element =>
{
return (
<div>
Sort by:
<Input type='select'
bsSize='sm'
value={this.props.settings.sortMode}
onChange={this.changeSortMode}>
<option value={SortMode.Timestamp}>Timestamp</option>
<option value={SortMode.AxisXNumeric}>X axis (numeric)</option>
<option value={SortMode.AxisXLexicographic}>X axis (lexicographic)</option>
</Input>
</div>
);
}
changeDateFormat = (e: React.ChangeEvent<HTMLInputElement>) =>
{
......@@ -47,10 +68,21 @@ export class XAxisSelector extends PureComponent<Props>
}
changeXAxis = (xAxis: string) =>
{
const sortMode = xAxis === 'timestamp' ? SortMode.Timestamp : this.props.settings.sortMode;
this.props.onChange({
...this.props.settings,
xAxis,
sortMode
});
}
changeSortMode = (e: React.ChangeEvent<HTMLInputElement>) =>
{
this.props.onChange({
...this.props.settings,
xAxis
sortMode: Number(e.currentTarget.value) as SortMode
});
}
}
import {DateFormat} from './date-format';
import {SortMode} from './sort-mode';
export interface XAxisSettings
{
xAxis: string;
dateFormat: DateFormat;
sortMode: SortMode;
}
......@@ -6,6 +6,7 @@ import {
linearizeGroups,
MeasurementGroup
} from '../../components/charts/chart/chart-utils';
import {SortMode} from '../../components/charts/chart/sort-mode';
import {compareDate} from '../../util/date';
import {exponentialAverage} from '../../util/math';
import {formatKey} from '../../util/measurement';
......@@ -38,7 +39,7 @@ export function calculateRelPerformance(view: View, measurements: Measurement[],
measurements, GroupMode.AxisX, axisX,
view.yAxes, dateFormat
);
const linearized = linearizeGroups(groups, dateFormat);
const linearized = linearizeGroups(groups, dateFormat, SortMode.Timestamp);
return {
groups: linearized,
......
import {PersistedState} from 'redux-persist/es/types';
import {SortMode} from '../../components/charts/chart/sort-mode';
import {XAxisSettings} from '../../components/charts/chart/x-axis-settings';
import {SessionState} from '../session/reducers';
import {lensPath, over} from 'ramda';
type SavedState = PersistedState & SessionState;
export const migrations = {
0: (state: SavedState) => state
0: (state: SavedState) => state,
1: (state: SavedState) => over(lensPath(['pages', 'chartState', 'xAxisSettings']), (settings: XAxisSettings) => ({
...settings,
sortMode: SortMode.Timestamp
}), state)
};
......@@ -20,7 +20,7 @@ export interface AppState
const sessionPersist = {
key: 'session',
version: 0,
version: 1,
storage,
migrate: createMigrate(migrations),
transforms: [
......
......@@ -2,6 +2,7 @@ import {compose} from 'redux';
import {reducerWithInitialState} from 'typescript-fsa-reducers';
import {DATE_FORMAT_DAY} from '../../../../components/charts/chart/date-format';
import {LineChartSettings} from '../../../../components/charts/chart/line-chart/line-chart-settings';
import {SortMode} from '../../../../components/charts/chart/sort-mode';
import {XAxisSettings} from '../../../../components/charts/chart/x-axis-settings';
import {createRequest, hookRequestActions, Request} from '../../../../util/request';
import {AppState} from '../../../app/reducers';
......@@ -27,7 +28,8 @@ const initialState: ChartPageState = {
selectedViews: [],
xAxisSettings: {
xAxis: '',
dateFormat: DATE_FORMAT_DAY
dateFormat: DATE_FORMAT_DAY,
sortMode: SortMode.Timestamp
},
lineChartSettings: {
connectPoints: true,
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment