Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Chart XY 위젯

Chart XY는 객체를 막대, 선 또는 산점도로 표시합니다. 속성별 집계와 세분화, 함수 집계 레이어, 축과 범례 및 선택 필터를 구성할 수 있습니다. 더 세밀한 문법 기반 시각화는 Vega Chart를 사용합니다.

레이어 추가와 입력

데이터를 표시하려면 Layer를 구성합니다. 레이어 Title은 작성자가 구성 패널에서 구분하는 이름이며 소비자 화면에는 표시하지 않습니다.

Data input입력
Object setWorkshop 객체 집합 변수
Function aggregation2차원 또는 3차원 집계를 반환하는 함수. 사용 가능한 레이어 설정은 일부로 제한됩니다.
Time series set시계열 집합 변수. X축은 시간 범위, Y축은 시계열값이며 Line Chart만 지원합니다.

일반 객체 입력의 Layer type은 Bar Chart, Line Chart 또는 Scatter Chart입니다. Line에는 Line, 아래 영역을 칠하는 Area와 세그먼트 값을 쌓는 Stacked가 있습니다. Labels는 Bar와 Line에서 값 레이블을 표시합니다.

속성과 series

X axis property에서 분류할 속성을 고릅니다. 한 속성에 단일 또는 여러 series를 표시할 수 있습니다. 예를 들어 Alert Type별 경고 수와 Hours Delayed 합계를 함께 볼 수 있습니다.

Series aggregation의 기본값은 Count입니다. Average, Min, Max, Sum과 Approximate Unique Count도 사용할 수 있습니다.

Segment by는 두 번째 속성으로 각 값을 나눕니다. Alert Type별 막대를 Aircraft Type으로 세분화하는 방식이 한 예입니다.

설정범위와 동작
Display of null/missing valuesLine 전용입니다. Gap은 빈 구간, Ignored는 앞뒤 관측값을 연결, Zeroes는 0으로 취급하여 표시합니다.
Display overrideSeries의 범례 이름을 바꾸거나 세분화한 차트의 특정 segment 이름을 바꿉니다.
Segment overridesBar 전용입니다. Stacked, Percentage와 Grouped 중 표현을 정합니다.
Selection as filterObject set 입력에만 제공하며 선택 결과를 Object Set Filter로 출력하여 후속 위젯에 적용합니다.
Delete layer현재 레이어를 제거합니다.

Zeroes는 선택한 표시 규칙이며 원천에 실제 0이 관측되었다는 뜻은 아닙니다.

시나리오 비교

Compare against Scenarios에 Scenario Array를 연결하면 시나리오별 값을 segment 축으로 비교합니다. 이 옵션을 켜면 다른 속성으로 Segment by를 동시에 지정할 수 없습니다.

한 레이어 안의 시나리오 색은 자동 배정하며 레이어 Color는 그 레이어의 모든 시나리오에 적용합니다. 시나리오마다 색을 따로 정하려면 레이어를 각각 만들고 각 레이어의 Load data from scenario를 사용합니다.

실행 중 시간 버킷 조정

X축이 Date 또는 Timestamp 속성이고 기본 집계를 쓰는 레이어에는 실행 중 차트 toolbar의 톱니바퀴가 나타납니다. 여기서 일, 주나 월 같은 timestamp bucket size를 고릅니다. 함수 집계 레이어나 다른 X축 유형에는 이 버튼을 제공하지 않습니다.

차트 전체 설정

구역설정
Categorical axis 제목Show title로 표시하거나 덮어씁니다. 기본값은 series의 속성 유형 이름입니다.
Categorical axis 숫자 서식그룹 구분, 소수점 최소/최대, 과학 표기 등을 구성합니다.
Categorical axis 정렬기본 A~Z입니다. 값 오름차순/내림차순 또는 Custom을 선택할 수 있습니다. Custom 순서는 속성에 숫자 정렬값을 부여하고 그 속성을 sort metric으로 지정합니다.
Value axis 여러 축여러 series가 있을 때 series별 축을 정할 수 있습니다. 서로 다른 값 범위를 표시하는 데 사용합니다.
Value axis 제목기본 집계 유형 이름을 표시하거나 바꿉니다.
Value axis 숫자 서식그룹 구분, 소수점 자릿수와 과학 표기 등을 지정합니다.
Value axis Scale type기본 Linear 또는 Logarithmic입니다.
Minimum bound / Maximum bound기본적으로 표시값에서 자동 계산합니다. Min 또는 Max로 수동 지정할 수 있습니다.
Legend표시 여부와 위치를 정합니다.
Bar orientationBar의 기본값은 Horizontal입니다. Line과 Scatter는 Vertical만 가능합니다.

함수 집계의 반환형

함수는 TwoDimensionalAggregation<Key, Value> 또는 ThreeDimensionalAggregation<Key, Segment, Value>를 반환합니다. 등록 방식과 내부 구조는 언어별로 다릅니다.

언어등록과 구조
TypeScript v1클래스 메서드에 @Function()을 붙입니다. 바깥은 buckets이며 3차원의 안쪽 배열은 value입니다.
TypeScript v2파일의 default export입니다. 버킷 배열을 직접 반환하고 3차원의 안쪽 배열은 groups입니다.
Python@function으로 등록합니다. 집계 클래스의 buckets와 SingleBucket 또는 NestedBucket을 사용합니다.

선언한 집계 타입을 올바르게 구성하면 같은 의미의 출력으로 직렬화하므로 위젯의 연결 방식을 바꿀 필요는 없습니다. TypeScript v2에서 v1의 { buckets: ... } 구조를 그대로 반환하면 배열형 반환 계약에 맞지 않습니다.

두 버킷의 자료형 예

다음은 bucket1에 5.0, bucket2에 6.0을 제공하는 고정 예시 함수입니다. 온톨로지 데이터를 조회한 결과를 대신하는 구현이 아니라 반환 구조를 보여 줍니다.

import { Double, Function, TwoDimensionalAggregation } from "@foundry/functions-api";

export class MyFunctions {
    @Function()
    public myTwoDimensionalAggregation(): TwoDimensionalAggregation<string, Double> {
        return {
            buckets: [
                { key: "bucket1", value: 5.0 },
                { key: "bucket2", value: 6.0 },
            ],
        };
    }
}
import { Double, TwoDimensionalAggregation } from "@osdk/functions";

function myTwoDimensionalAggregationFunction(): TwoDimensionalAggregation<string, Double> {
    return [
        { key: "bucket1", value: 5.0 },
        { key: "bucket2", value: 6.0 },
    ];
}

export default myTwoDimensionalAggregationFunction;
from functions.api import (
    function,
    Double,
    TwoDimensionalAggregation,
    SingleBucket
)

@function
def my_two_dimensional_aggregation_function() -> TwoDimensionalAggregation[str, Double]:
    return TwoDimensionalAggregation(
        buckets=[
            SingleBucket(key="bucket1", value=Double(5.0)),
            SingleBucket(key="bucket2", value=Double(6.0)),
        ]
    )

일별 비율 집계 예

가상의 MyObjectType에 Timestamp date와 숫자 value 및 total이 있다고 가정합니다. 같은 날짜의 value 합을 total 합으로 나눕니다. 함수는 비율을 반환하며 백분율 표현은 표시 서식에서 정합니다.

이 예제는 합계 분모가 0인 버킷의 표시값을 0으로 정합니다. 0으로 나누기의 수학적 결과가 아니라 예제에서 선택한 경계 처리입니다. 없는 분모 버킷을 0으로 채우는 것과 구분합니다.

TypeScript v1의 범위 키는 IRange<Timestamp>의 min/max입니다. 따로 수행한 집계를 배열 위치로 나누지 않고 같은 날짜 키로 대응시킵니다. 날짜나 대응 분모가 없으면 오류를 보고합니다.

import {
    Double, Function, IRange, Timestamp, TwoDimensionalAggregation, UserFacingError,
} from "@foundry/functions-api";
import { MyObjectType, ObjectSet } from "@foundry/ontology-api";

function bucketStart(range: IRange<Timestamp>): number {
    if (range.min === undefined) throw new UserFacingError("날짜 버킷의 시작이 없습니다.");
    return range.min.getTime();
}

export class DailyRatioFunctions {
    @Function()
    public async percentOfTotal(
        objects: ObjectSet<MyObjectType>,
    ): Promise<TwoDimensionalAggregation<IRange<Timestamp>, Double>> {
        const [numerators, denominators] = await Promise.all([
            objects.groupBy(object => object.date.byDays()).sum(object => object.value),
            objects.groupBy(object => object.date.byDays()).sum(object => object.total),
        ]);
        const totals = new Map<number, Double>();
        for (const bucket of denominators.buckets) totals.set(bucketStart(bucket.key), bucket.value);
        const buckets: TwoDimensionalAggregation<IRange<Timestamp>, Double>["buckets"] = [];
        for (const bucket of numerators.buckets) {
            const total = totals.get(bucketStart(bucket.key));
            if (total === undefined) throw new UserFacingError("대응하는 분모 날짜 버킷이 없습니다.");
            buckets.push({ key: bucket.key, value: total === 0 ? 0 : bucket.value / total });
        }
        return { buckets };
    }
}

TypeScript v2의 aggregate 결과는 평평한 행 배열입니다. $group에서 그룹 키를, row.value.sum과 row.total.sum에서 지표를 가져옵니다. 속성값이 없어 지표가 빠진 경우에는 이 예제의 합계값을 0으로 처리합니다.

같은 입력과 그룹에서 두 합계를 구하므로 한 요청의 $select에 두 지표를 넣습니다. 별도로 요청한다면 unordered 결과의 순서가 같다는 보장이 없어 그룹 키로 연결해야 합니다.

$duration의 그룹값은 버킷 시작 시각 하나입니다. 아래의 1일 예는 시작에 24시간을 더하여 Range<TimestampISOString>의 startValue/endValue를 만듭니다. 범위형 반환과 $ranges 입력의 [start, end] 튜플은 구분합니다.

import { ObjectSet } from "@osdk/client";
import { Double, Range, TimestampISOString, TwoDimensionalAggregation } from "@osdk/functions";
import { MyObjectType } from "@ontology/sdk";

const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;

function toDayRange(start: Date): Range<TimestampISOString> {
    return {
        startValue: start.toISOString(),
        endValue: new Date(start.getTime() + ONE_DAY_IN_MS).toISOString(),
    };
}

export default async function percentOfTotal(
    objects: ObjectSet<MyObjectType>,
): Promise<TwoDimensionalAggregation<Range<TimestampISOString>, Double>> {
    const rows = await objects.aggregate({
        $select: { "value:sum": "unordered", "total:sum": "unordered" },
        $groupBy: { date: { $duration: [1, "days"] } },
    });
    const buckets: TwoDimensionalAggregation<Range<TimestampISOString>, Double> = [];
    for (const row of rows) {
        if (row.$group.date == null) continue;
        const numerator = row.value.sum ?? 0;
        const denominator = row.total.sum ?? 0;
        buckets.push({
            key: toDayRange(new Date(row.$group.date)),
            value: denominator === 0 ? 0 : numerator / denominator,
        });
    }
    return buckets;
}

날짜와 groupId별 비율

TypeScript v1은 segmentBy로 두 번째 차원을 만듭니다. 아래는 가상의 String groupId를 사용하며 날짜와 groupId를 모두 맞춰 나눕니다. 앞 예와 같이 분모 0은 0을 표시하고, 대응 버킷 자체가 없으면 오류를 반환합니다.

import {
    Double, Function, IRange, ThreeDimensionalAggregation, Timestamp, UserFacingError,
} from "@foundry/functions-api";
import { MyObjectType, ObjectSet } from "@foundry/ontology-api";

function bucketStart(range: IRange<Timestamp>): number {
    if (range.min === undefined) throw new UserFacingError("날짜 버킷의 시작이 없습니다.");
    return range.min.getTime();
}

export class SegmentedRatioFunctions {
    @Function()
    public async percentOfTotalSegmented(
        objects: ObjectSet<MyObjectType>,
    ): Promise<ThreeDimensionalAggregation<IRange<Timestamp>, string, Double>> {
        const [numerators, denominators] = await Promise.all([
            objects.groupBy(object => object.date.byDays())
                .segmentBy(object => object.groupId.topValues()).sum(object => object.value),
            objects.groupBy(object => object.date.byDays())
                .segmentBy(object => object.groupId.topValues()).sum(object => object.total),
        ]);
        const totals = new Map<number, Map<string, Double>>();
        for (const bucket of denominators.buckets) {
            const segments = new Map<string, Double>();
            for (const segment of bucket.value) segments.set(segment.key, segment.value);
            totals.set(bucketStart(bucket.key), segments);
        }
        const buckets: ThreeDimensionalAggregation<IRange<Timestamp>, string, Double>["buckets"] = [];
        for (const bucket of numerators.buckets) {
            const denominatorsBySegment = totals.get(bucketStart(bucket.key));
            if (denominatorsBySegment === undefined) {
                throw new UserFacingError("대응하는 분모 날짜 버킷이 없습니다.");
            }
            const value: Array<{ key: string; value: Double }> = [];
            for (const segment of bucket.value) {
                const denominator = denominatorsBySegment.get(segment.key);
                if (denominator === undefined) {
                    throw new UserFacingError("대응하는 분모 세그먼트가 없습니다.");
                }
                value.push({
                    key: segment.key,
                    value: denominator === 0 ? 0 : segment.value / denominator,
                });
            }
            buckets.push({ key: bucket.key, value });
        }
        return { buckets };
    }
}

TypeScript v2에는 별도 segmentBy가 없습니다. $groupBy에 날짜와 groupId를 함께 넣으면 각 조합의 평평한 행을 받으므로 날짜 아래 groups로 묶습니다. 여러 그룹 키를 사용할 때 모든 $select 값은 unordered여야 합니다.

import { ObjectSet } from "@osdk/client";
import { Double, Range, ThreeDimensionalAggregation, TimestampISOString } from "@osdk/functions";
import { MyObjectType } from "@ontology/sdk";

const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
type DayBucket = {
    key: Range<TimestampISOString>;
    groups: Array<{ key: string; value: Double }>;
};

function toDayRange(start: Date): Range<TimestampISOString> {
    return {
        startValue: start.toISOString(),
        endValue: new Date(start.getTime() + ONE_DAY_IN_MS).toISOString(),
    };
}

export default async function percentOfTotalSegmented(
    objects: ObjectSet<MyObjectType>,
): Promise<ThreeDimensionalAggregation<Range<TimestampISOString>, string, Double>> {
    const rows = await objects.aggregate({
        $select: { "value:sum": "unordered", "total:sum": "unordered" },
        $groupBy: { date: { $duration: [1, "days"] }, groupId: "exact" },
    });
    const byDate = new Map<number, DayBucket>();
    for (const row of rows) {
        if (row.$group.date == null || row.$group.groupId == null) continue;
        const start = new Date(row.$group.date);
        const startMillis = start.getTime();
        let bucket = byDate.get(startMillis);
        if (bucket === undefined) {
            bucket = { key: toDayRange(start), groups: [] };
            byDate.set(startMillis, bucket);
        }
        const numerator = row.value.sum ?? 0;
        const denominator = row.total.sum ?? 0;
        bucket.groups.push({
            key: row.$group.groupId,
            value: denominator === 0 ? 0 : numerator / denominator,
        });
    }
    return Array.from(byDate.values());
}

버킷 수와 정확도

TSv1 topValues의 기본 버킷 수는 1,000, TSv2 exact는 10,000입니다. 고유 값이 이 범위 안에 있을 때만 해당 그룹화를 정확하게 유지할 수 있습니다. 초과하면 일부 객체가 반환 그룹에서 제외되어 근사 결과가 될 수 있습니다.

플랫폼의 정확/근사 정보는 TypeScript OSDK에서 노출하지 않습니다. 높은 cardinality에서는 필요한 버킷 수 n을 $exactWithLimit로 지정하는 구성을 검토합니다. 버킷 수를 키우는 비용과 실제 대상 범위도 함께 고려합니다.

Python 사용자 집계

Python은 TwoDimensionalAggregation과 ThreeDimensionalAggregation 클래스의 구조를 사용합니다. Python Ontology SDK v2에서는 from_osdk 변환을 사용할 수 있습니다. 3차원 변환의 ThreeDimensionalAggregation.from_osdk(result, "date", "groupId")처럼 group-by 속성 API 이름을 전달하며, 주변 Python 식별자가 snake_case여도 API 이름은 해당 camelCase를 유지합니다.

Python 사용자 집계에 전체 예가 있고 TypeScript v1 사용자 집계에는 범위를 다루는 추가 예가 있습니다. 시계열 변수의 변환과 요약은 시계열 계산을 따릅니다.