Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rangeSelection feature on LineType, Column Chart #763

Merged
merged 6 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions apps/chart/src/charts/areaChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import AxisTitle from '@src/component/axisTitle';
import ExportMenu from '@src/component/exportMenu';
import ResetButton from '@src/component/resetButton';
import Legend from '@src/component/legend';
import Zoom from '@src/component/zoom';
import RangeSelection from '@src/component/rangeSelection';
import SelectedSeries from '@src/component/selectedSeries';
import Background from '@src/component/background';
import NoDataText from '@src/component/noDataText';
Expand Down Expand Up @@ -67,6 +67,7 @@ import { AreaChartProps, SelectSeriesInfo } from '@t/charts';
* @param {boolean} [props.options.series.showDot=false] - Whether to show dot or not.
* @param {boolean} [props.options.series.spline=false] - Whether to make spline chart or not.
* @param {boolean} [props.options.series.zoomable=false] - Whether to use zoom feature or not.
* @param {boolean} [props.options.series.rangeSelectable=false] - Whether to use range selection feature or not.
* @param {string} [props.options.series.eventDetectType] - Event detect type. 'near', 'nearest', 'grouped', 'point' is available.
* @param {boolean} [props.options.series.shift=false] - Whether to use shift when addData or not.
* @param {Object} [props.options.series.dataLabels] - Set the visibility, location, and formatting of dataLabel. For specific information, refer to the {@link https://github.com/nhn/tui.chart|DataLabels guide} on github.
Expand Down Expand Up @@ -158,8 +159,8 @@ export default class AreaChart extends Chart<AreaChartOptions> {
this.componentManager.add(HoveredSeries);
this.componentManager.add(SelectedSeries);
this.componentManager.add(Tooltip, { chartEl: this.el });
this.componentManager.add(Zoom);
this.componentManager.add(ResetButton);
this.componentManager.add(RangeSelection);
this.componentManager.add(NoDataText);

this.painter.addGroups([
Expand Down
5 changes: 4 additions & 1 deletion apps/chart/src/charts/columnChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import HoveredSeries from '@src/component/hoveredSeries';
import SelectedSeries from '@src/component/selectedSeries';
import Background from '@src/component/background';
import NoDataText from '@src/component/noDataText';
import RangeSelection from '@src/component/rangeSelection';

import * as basicBrush from '@src/brushes/basic';
import * as axisBrush from '@src/brushes/axis';
Expand Down Expand Up @@ -60,7 +61,8 @@ import { ColumnChartProps, SelectSeriesInfo } from '@t/charts';
* @param {number|string} [props.options.chart.width] - Chart width. 'auto' or if not write, the width of the parent container is followed. 'auto' or if not created, the width of the parent container is followed.
* @param {number|string} [props.options.chart.height] - Chart height. 'auto' or if not write, the width of the parent container is followed. 'auto' or if not created, the height of the parent container is followed.
* @param {Object} [props.options.series]
* @param {boolean} [props.options.series.selectable=false] - Whether to make selectable series or not.
* @param {boolean} [props.options.series.selectable=false]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

설명이 사라졌네요

* @param {boolean} [props.options.series.rangeSelectable=false] - Whether to use range selection feature or not.
* @param {number} [props.options.series.barWidth] - Bar width.
* @param {boolean} [props.options.series.diverging] - Whether to use diverging chart or not.
* @param {Object} [props.options.series.stack] - Option to use the stack chart or, if so, what type of stack to use.
Expand Down Expand Up @@ -153,6 +155,7 @@ export default class ColumnChart extends Chart<ColumnChartOptions> {
this.componentManager.add(DataLabels);
this.componentManager.add(Tooltip, { chartEl: this.el });
this.componentManager.add(NoDataText);
this.componentManager.add(RangeSelection);

this.painter.addGroups([
basicBrush,
Expand Down
2 changes: 2 additions & 0 deletions apps/chart/src/charts/columnLineChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import HoveredSeries from '@src/component/hoveredSeries';
import DataLabels from '@src/component/dataLabels';
import Tooltip from '@src/component/tooltip';
import Background from '@src/component/background';
import RangeSelection from '@src/component/rangeSelection';
import NoDataText from '@src/component/noDataText';

import * as basicBrush from '@src/brushes/basic';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기는 rangeSelectable 설명이 없어도 되나요??

Expand Down Expand Up @@ -171,6 +172,7 @@ export default class ColumnLineChart extends Chart<ColumnLineChartOptions> {
this.componentManager.add(HoveredSeries);
this.componentManager.add(SelectedSeries);
this.componentManager.add(DataLabels);
this.componentManager.add(RangeSelection);
this.componentManager.add(Tooltip, { chartEl: this.el });
this.componentManager.add(NoDataText);

Expand Down
5 changes: 3 additions & 2 deletions apps/chart/src/charts/lineAreaChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Title from '@src/component/title';
import ExportMenu from '@src/component/exportMenu';
import SelectedSeries from '@src/component/selectedSeries';
import HoveredSeries from '@src/component/hoveredSeries';
import Zoom from '@src/component/zoom';
import RangeSelection from '@src/component/rangeSelection';
import ResetButton from '@src/component/resetButton';
import Background from '@src/component/background';
import NoDataText from '@src/component/noDataText';
Expand Down Expand Up @@ -69,6 +69,7 @@ import { LineAreaChartProps, AddSeriesDataInfo, SelectSeriesInfo } from '@t/char
* @param {Object} [props.options.series.line] - Options to be applied to the line chart. 'spline', 'showDot', 'dataLabels' is available. For specific information, refer to the {@link https://github.com/nhn/tui.chart|Line Chart guide} on github.
* @param {Object} [props.options.series.area] - Options to be applied to the area chart. 'stack', 'spline', 'showDot', 'dataLabels' is available. For specific information, refer to the {@link https://github.com/nhn/tui.chart|Area Chart guide} on github.
* @param {boolean} [props.options.series.zoomable=false] - Whether to use zoom feature or not.
* @param {boolean} [props.options.series.rangeSelectable=false] - Whether to use range selection feature or not.
* @param {boolean} [props.options.series.showDot=false] - Whether to show dot or not.
* @param {boolean} [props.options.series.spline=false] - Whether to make spline chart or not.
* @param {boolean} [props.options.series.selectable=false] - Whether to make selectable series or not.
Expand Down Expand Up @@ -160,7 +161,7 @@ export default class LineAreaChart extends Chart<LineAreaChartOptions> {
this.componentManager.add(HoveredSeries);
this.componentManager.add(SelectedSeries);
this.componentManager.add(Tooltip, { chartEl: this.el });
this.componentManager.add(Zoom);
this.componentManager.add(RangeSelection);
this.componentManager.add(ResetButton);
this.componentManager.add(NoDataText);

Expand Down
5 changes: 3 additions & 2 deletions apps/chart/src/charts/lineChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import AxisTitle from '@src/component/axisTitle';
import Title from '@src/component/title';
import ExportMenu from '@src/component/exportMenu';
import HoveredSeries from '@src/component/hoveredSeries';
import Zoom from '@src/component/zoom';
import RangeSelection from '@src/component/rangeSelection';
import ResetButton from '@src/component/resetButton';
import SelectedSeries from '@src/component/selectedSeries';
import Background from '@src/component/background';
Expand Down Expand Up @@ -66,6 +66,7 @@ import { LineChartProps, SelectSeriesInfo } from '@t/charts';
* @param {boolean} [props.options.series.showDot=false] - Whether to show dot or not.
* @param {boolean} [props.options.series.spline=false] - Whether to make spline chart or not.
* @param {boolean} [props.options.series.zoomable=false] - Whether to use zoom feature or not.
* @param {boolean} [props.options.series.rangeSelectable=false] - Whether to use range selection feature or not.
* @param {string} [props.options.series.eventDetectType] - Event detect type. 'near', 'nearest', 'grouped', 'point' is available.
* @param {boolean} [props.options.series.shift=false] - Whether to use shift when addData or not.
* @param {Object} [props.options.series.dataLabels] - Set the visibility, location, and formatting of dataLabel. For specific information, refer to the {@link https://github.com/nhn/tui.chart|DataLabels guide} on github.
Expand Down Expand Up @@ -156,7 +157,7 @@ export default class LineChart extends Chart<LineChartOptions> {
this.componentManager.add(HoveredSeries);
this.componentManager.add(SelectedSeries);
this.componentManager.add(Tooltip, { chartEl: this.el });
this.componentManager.add(Zoom);
this.componentManager.add(RangeSelection);
this.componentManager.add(ResetButton);
this.componentManager.add(NoDataText);

Expand Down
4 changes: 2 additions & 2 deletions apps/chart/src/charts/lineScatterChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Title from '@src/component/title';
import ExportMenu from '@src/component/exportMenu';
import SelectedSeries from '@src/component/selectedSeries';
import HoveredSeries from '@src/component/hoveredSeries';
import Zoom from '@src/component/zoom';
import RangeSelection from '@src/component/rangeSelection';
import Background from '@src/component/background';
import NoDataText from '@src/component/noDataText';

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 rangeSelectable 설명이 없네요

Copy link
Contributor Author

@jwlee1108 jwlee1108 Nov 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 Zoom 기능 지원이 제대로 안되는 차트인데 들어가 있었네요. 이번 PR에선 빼고 zoom 처리를 다시 확인해봐야겠어요.

(추가)@ts-ignore 걸고 zoomable 사용하는 사람들이 있을 수 있어서 우선 이 상태로 둘게요.

Expand Down Expand Up @@ -146,7 +146,7 @@ export default class LineScatterChart extends Chart<LineScatterChartOptions> {
this.componentManager.add(HoveredSeries);
this.componentManager.add(SelectedSeries);
this.componentManager.add(Tooltip, { chartEl: this.el });
this.componentManager.add(Zoom);
this.componentManager.add(RangeSelection);
this.componentManager.add(NoDataText);

this.painter.addGroups([
Expand Down
4 changes: 2 additions & 2 deletions apps/chart/src/component/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ export type ComponentType =
| 'exportMenu'
| 'resetButton'
| 'zeroAxis'
| 'zoom'
| 'backButton'
| 'background'
| 'noDataText';
| 'noDataText'
| 'rangeSelection';

type ComponentModels =
| AxisModels
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ import { AxisData, ChartState, LabelAxisData, Options, Scale, Series } from '@t/
import { RectResponderModel, RectModel } from '@t/components/series';
import { isNull, range } from '@src/helpers/utils';
import { sortNumber } from '@src/helpers/utils';
import { ZoomModels } from '@t/components/zoom';
import { AreaSeriesType, CoordinateDataType, LineSeriesType, Point } from '@t/options';
import { RangeSelectionModels } from '@t/components/rangeSelection';
import {
AreaSeriesType,
ColumnChartBoxSeriesOptions,
CoordinateDataType,
LineSeriesType,
LineTypeSeriesOptions,
Point,
} from '@t/options';
import { makeObservableObjectToNormal } from '@src/store/reactive';
import {
getCoordinateDataIndex,
Expand All @@ -19,10 +26,11 @@ import {

const DRAG_MIN_WIDTH = 15;

type ZoomableSeries = Pick<Series, 'line' | 'area'>;
type RangeSelectableSeries = Pick<Series, 'line' | 'area' | 'column'>;
type RangeSelectableSeriesOptions = ColumnChartBoxSeriesOptions | LineTypeSeriesOptions;

export default class Zoom extends Component {
models: ZoomModels = { selectionArea: [] };
export default class RangeSelection extends Component {
models: RangeSelectionModels = { selectionArea: [] };

responders!: RectResponderModel[];

Expand All @@ -35,13 +43,14 @@ export default class Zoom extends Component {
private isDragging = false;

initialize() {
this.type = 'zoom';
this.type = 'rangeSelection';
}

render(state: ChartState<Options>, computed) {
if (!state.zoomRange) {
if (!state.selectionRange && !state.zoomRange) {
return;
}

this.resetSelectionArea();
const { viewRange } = computed;
const { layout, axes, series, scale } = state;
Expand All @@ -65,7 +74,7 @@ export default class Zoom extends Component {
}

getRectResponderInfoForCoordinateType(
series: ZoomableSeries,
series: RangeSelectableSeries,
scale: Scale,
axisData: LabelAxisData,
categories: string[]
Expand Down Expand Up @@ -123,8 +132,17 @@ export default class Zoom extends Component {
.sort((a, b) => a.index! - b.index!)
.map((m) => m.data?.value);

this.store.dispatch('zoom', dragRange);
this.eventBus.emit('zoom', makeObservableObjectToNormal(dragRange));
const { series, options } = this.store.state;
const { series: seriesOptions } = options;

if (!series.column && (seriesOptions as LineTypeSeriesOptions)?.zoomable) {
this.store.dispatch('zoom', dragRange);
this.eventBus.emit('zoom', makeObservableObjectToNormal(dragRange));
}

if ((seriesOptions as RangeSelectableSeriesOptions)?.rangeSelectable) {
this.eventBus.emit('rangeSelection', dragRange);
}

this.eventBus.emit('resetHoveredSeries');
this.eventBus.emit('hideTooltip');
Expand Down
51 changes: 38 additions & 13 deletions apps/chart/src/store/legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ type LegendSizeParams = {
chart: Size;
};

type LegendInfoToSetIndexParams = {
legendData: LegendDataList;
rowCount: number;
columnCount: number;
legendCount: number;
verticalAlign: boolean;
itemHeight: number;
};

const INITIAL_LEGEND_WIDTH = 100;
const INITIAL_CIRCLE_LEGEND_WIDTH = 150;
const COMPONENT_HEIGHT_EXCEPT_Y_AXIS = 100;
Expand All @@ -84,18 +93,24 @@ const NUMBER_OF_BOTH_SIDES = 2;

function recalculateLegendWhenHeightOverflows(params: LegendSizeParams, legendHeight: number) {
const { legendWidths, itemHeight } = params;
const totalHeight = legendWidths.length * itemHeight;
const columnCount = Math.ceil(totalHeight / legendHeight);
const rowCount = legendWidths.length / columnCount;
const maxCountByColumn = Math.floor(legendHeight / itemHeight);
const columnCount = Math.ceil(legendWidths.length / maxCountByColumn);
let legendWidth = 0;

range(0, columnCount).forEach((count) => {
legendWidth += Math.max(...legendWidths.slice(count * rowCount, (count + 1) * rowCount));
legendWidth += Math.max(
...legendWidths.slice(count * maxCountByColumn, (count + 1) * maxCountByColumn)
);
});

legendWidth += LEGEND_ITEM_MARGIN_X * (columnCount - 1);

return { legendWidth, legendHeight: rowCount * itemHeight + padding.Y, columnCount, rowCount };
return {
legendWidth,
legendHeight: maxCountByColumn * itemHeight + padding.Y,
columnCount,
rowCount: maxCountByColumn,
};
}

function recalculateLegendWhenWidthOverflows(params: LegendSizeParams, prevLegendWidth: number) {
Expand Down Expand Up @@ -446,13 +461,16 @@ function getNextColumnRowIndex(params: {
return [rowIndex, columnIndex];
}

function setIndexToLegendData(
legendData: LegendDataList,
rowCount: number,
columnCount: number,
legendCount: number,
verticalAlign: boolean
) {
function setIndexToLegendData(legendInfoParams: LegendInfoToSetIndexParams) {
const {
legendData,
rowCount,
columnCount,
legendCount,
verticalAlign,
itemHeight,
} = legendInfoParams;

let columnIndex = 0;
let rowIndex = 0;

Expand Down Expand Up @@ -550,7 +568,14 @@ const legend: StoreModule = {
circleLegendVisible,
});

setIndexToLegendData(legendData, rowCount, columnCount, legendWidths.length, verticalAlign);
setIndexToLegendData({
legendData,
rowCount,
columnCount,
verticalAlign,
itemHeight,
legendCount: legendWidths.length,
});

extend(state.legend, {
visible,
Expand Down
21 changes: 21 additions & 0 deletions apps/chart/src/store/seriesData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ function initRange(series: RawSeries, categories?: Categories): RangeDataType<nu
return [0, rawCategoriesLength - 1];
}

function initSelectionRange(series: RawSeries, options: Options, categories?: Categories) {
if (
!(series.line || series.area || series.column) ||
!(options.series as LineTypeSeriesOptions)?.rangeSelectable
) {
return;
}

return initRange(series, categories);
}

function initZoomRange(series: RawSeries, options: Options, categories?: Categories) {
if (!(series.line || series.area) || !(options.series as LineTypeSeriesOptions)?.zoomable) {
return;
Expand Down Expand Up @@ -173,6 +184,7 @@ const seriesData: StoreModule = {
series: {
...series,
} as Series,
selectionRange: initSelectionRange(series, options, categories),
zoomRange: initZoomRange(series, options, categories),
shiftRange: initShiftRange(series, options, categories),
disabledSeries: initDisabledSeries(series),
Expand Down Expand Up @@ -245,6 +257,15 @@ const seriesData: StoreModule = {
this.notify(state, 'axes');
}
},
selection({ state }, rangeCategories: string[]) {
const rawCategories = state.rawCategories as string[];

state.selectionRange = rangeCategories.map((rangeCategory) =>
rawCategories.findIndex((category) => category === rangeCategory)
) as RangeDataType<number>;

this.notify(state, 'selectionRange');
},
zoom({ state }, rangeCategories: string[]) {
const rawCategories = state.rawCategories as string[];

Expand Down
20 changes: 20 additions & 0 deletions apps/chart/stories/area.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,26 @@ export const zoomable = () => {
return el;
};

export const rangeSelectable = () => {
const { el, chart } = createChart(avgTemperatureData, {
series: {
rangeSelectable: true,
dataLabels: {
visible: true,
},
},
xAxis: {
pointOnColumn: false,
},
});

chart.on('rangeSelection', (selectedRange) => {
console.log(selectedRange);
});

return el;
};

export const selectable = () => {
const { el } = createChart(avgTemperatureData, {
chart: { title: 'Average Temperature' },
Expand Down
Loading