UNPKG

@aptpod/data-viz-create-visual-parts-react

Version:

template of npm project with typescript

250 lines (222 loc) 7.41 kB
import { ViewGrid, ViewBox, PlaybackTimeSelectedRange, DataSpecification, Value, } from '@aptpod/data-viz-visual-parts-sdk' import { ComponentProps, useMemo, useCallback, useEffect } from 'react' import { BIND_DATA_COUNT_MAX } from './constant' import { LineGraph } from './component' import { Extension } from './extension' import { shouldContentFit, buildColorWithOpacity, arrayIncludes, arraySearchIndexLe, arraySearchIndexGe, } from './utils' import { buildEmptyArray, selectNonNullables } from 'src/utils/array' import { memoize } from 'src/utils/memoize' /** * LineGraph ComponentのPropsの型 */ type LineGraphProps = ComponentProps<typeof LineGraph> type LineGraphPropsValue = LineGraphProps['values'][0] /** * DataSpecificationで使用するフィールドの型のみで再定義 */ type UsedDataSpecification = Pick< DataSpecification, | 'id' | 'rangeMin' | 'rangeMax' | 'dataName' | 'decimalDigits' | 'colorCode' | 'colorOpacity' | 'colorFill' > /** * Valueで使用するフィールドの型のみで再定義 */ type UsedValue = Pick<Value, 'data'> /** * ValueのData型からUNIX Timestampのフィールドのみで型を再定義 */ type UsedValuePickedTime = Pick<UsedValue['data'][0], 't'> /** * LineGraph ComponentのPropsに変換します。 * メモ化のため、useMemo、memoize を使用しています。 * memoizeは、可変引数、loop内で使用するfunctionのメモ化で使用しています。 */ export const useSelectProps = (params: { viewGrid: ViewGrid viewBox: ViewBox playbackTimeSelectedRange: PlaybackTimeSelectedRange extension: Extension dataSpecifications: UsedDataSpecification[] values: UsedValue[] onExtensionPatch: (patch: Partial<Extension>) => void }): LineGraphProps => { const { viewGrid, viewBox, playbackTimeSelectedRange, extension, dataSpecifications, values, onExtensionPatch, } = params // メモリのラベルを表示せずにビジュアルパーツのパネルにフィットするか // viewGridを使用して判定します。 // メモ化のため、useMemoを使用します。 const gotContentFit = useMemo(() => { return shouldContentFit(viewGrid.colSpan, viewGrid.rowSpan) }, [viewGrid.colSpan, viewGrid.rowSpan]) // 表示するピクセルサイズをviewBoxを使用して作成します。 // メモ化のため、useMemoを使用します。 const gotSize = useMemo(() => { return { width: viewBox.width, height: viewBox.height, } }, [viewBox.width, viewBox.height]) // 表示する時間範囲をplaybackTimeSelectedRangeを使用して作成します。 // メモ化のため、useMemoを使用します。 const gotTime = useMemo(() => { return { start: playbackTimeSelectedRange.start, end: playbackTimeSelectedRange.end, } }, [playbackTimeSelectedRange.end, playbackTimeSelectedRange.start]) // selectNonNullablesのメモ化 const memoizedSelectNonNullables = useMemo( () => memoize(selectNonNullables), [], ) // PropsValueを生成するためのメモ化済みfunctionの初期化 const memoizedPropsValues = useMemo(() => { return buildEmptyArray(BIND_DATA_COUNT_MAX).map(() => ({ buildColorWithOpacity: memoize(buildColorWithOpacity), hiddenIDsIncludes: memoize(arrayIncludes), exactValueDatas: memoize(exactValueDatas), buildPropsValue: memoize(buildPropsValue), })) }, []) // Props Values を取得します。 // メモ化したfunctionを使用します。 const gotValues = memoizedSelectNonNullables( ...buildEmptyArray(BIND_DATA_COUNT_MAX).map((_, i) => selectPropsValue( playbackTimeSelectedRange.start, playbackTimeSelectedRange.end, dataSpecifications[i], values[i], extension.hiddenIDs, memoizedPropsValues[i].buildColorWithOpacity, memoizedPropsValues[i].hiddenIDsIncludes, memoizedPropsValues[i].exactValueDatas, memoizedPropsValues[i].buildPropsValue, ), ), ) // 凡例をクリックしたときのイベントハンドラを定義します。 // メモ化のため useCallback を使用します。 const onLegendHiddenChange: LineGraphProps['onLegendHiddenChange'] = useCallback( ({ valueID, hidden }) => { const newHiddenIDs = !hidden ? extension.hiddenIDs.concat([valueID]) : extension.hiddenIDs.filter((id) => id !== valueID) onExtensionPatch({ hiddenIDs: newHiddenIDs }) }, [extension.hiddenIDs, onExtensionPatch], ) // Data Visualizerのバインドデータ削除によりhiddenIDsにゴミが残らないようにケアします。 useEffect(() => { const dataSpecIDs = dataSpecifications.map(({ id }) => id) const newHiddenIDs = extension.hiddenIDs.filter((hiddenID) => dataSpecIDs.includes(hiddenID), ) if (extension.hiddenIDs.length !== newHiddenIDs.length) { onExtensionPatch({ hiddenIDs: newHiddenIDs }) } }, [dataSpecifications, extension.hiddenIDs, onExtensionPatch]) return { contentFit: gotContentFit, size: gotSize, time: gotTime, values: gotValues, onLegendHiddenChange, } } /** * LineGraph Component Propsに含むValueの型に変換します。 * dataSpecification, value のいずれかが未定義の場合はnullを返します。 */ const selectPropsValue = ( startTime: number, endTime: number, dataSpecification: UsedDataSpecification | undefined, value: UsedValue | undefined, hiddenIDs: string[], funcBuildColorWithOpacity: typeof buildColorWithOpacity, funcHiddenIDInclues: typeof arrayIncludes, funcExactValueDatas: typeof exactValueDatas, funcBuildPropsValue: typeof buildPropsValue, ): LineGraphPropsValue | null => { if (!dataSpecification || !value) { return null } const { id, dataName, colorCode, colorOpacity, colorFill } = dataSpecification const { data } = value // RGB, またはRGBAのカラーコードを作成します。作成できないときはwhiteとみなします。 const color = funcBuildColorWithOpacity(colorCode, colorOpacity) ?? 'white' // 非表示状態を判定します。 const hidden = funcHiddenIDInclues(hiddenIDs, id) // 時間範囲指定のデータポイント配列を抽出します。 const gotDatas = funcExactValueDatas(data, startTime, endTime) return funcBuildPropsValue(id, dataName, color, colorFill, hidden, gotDatas) } /** * 時間範囲を指定してデータポイント配列を取得します。 */ export const exactValueDatas = ( datas: UsedValue['data'], startTime: number, endTime: number, ): UsedValue['data'] => { const compiler = (a: UsedValuePickedTime, b: UsedValuePickedTime) => a.t - b.t const startIndex = arraySearchIndexGe<UsedValuePickedTime>( datas, { t: startTime }, compiler, ) const endIndex = arraySearchIndexLe<UsedValuePickedTime>( datas, { t: endTime }, compiler, ) if (startIndex < 0 || endIndex < 0) { return [] } return datas.slice(startIndex, endIndex + 1) } /** * PropsValueのObjectを作成します。 */ const buildPropsValue = ( id: string, name: string, color: string, fill: boolean, hidden: boolean, datas: UsedValue['data'], ): LineGraphPropsValue => ({ id, name, color, fill, hidden, datas, })