스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Swift Charts: 기준을 높이다
데이터 시각화 심층 분석: Swift Charts와 SwiftUI를 사용하여 폭넓은 차트 옵션을 통해 앱에서 복잡한 데이터세트를 표현하는 방법을 알아보세요. 다양한 종류의 데이터를 표시하고 마크를 작성하여 보다 정교한 차트를 만드는 방법을 보여드리겠습니다. 또한 앱에 맞는 스타일의 차트를 만들 수 있도록 Swift Charts의 광범위한 차트 맞춤화 API에 대해 안내합니다. 이 세션을 최대한 활용하려면 WWDC22의 ‘Hello Swift Charts(Swift Charts 소개)'를 시청하시기 바랍니다.
리소스
관련 비디오
Tech Talks
WWDC22
-
다운로드
♪ 안녕하세요, Donghao입니다 이번 세션에서는 Swift Charts를 이용한 데이터 시각화 방법을 더 자세히 알아보려 합니다 다시 말해 앱에서 차트를 더 심도 깊이 활용해 보죠 훌륭한 데이터 시각화는 앱을 더욱 유익하고 매력적으로 만듭니다 여러분의 앱에 멋진 차트를 만들려면 고려할 것이 많습니다 물론 우리가 만드는 차트는 기본 데이터를 충실히 전달하고 쉽게 접근할 수 있어야 합니다 그러나 앱의 차트는 홀로 존재하지 않습니다 유저 인터페이스의 일부죠 다크 모드 같은 OS 기능과 현지화를 지원해야 합니다 차트는 나머지 UI와 매끄럽게 어울리도록 적절한 레이아웃이 필요합니다 Dynamic Type과 장치 화면 크기도 고려하고요 그리고 차트는 모든 플랫폼에서 작동하고 애니메이션의 모양과 느낌도 깔끔해야 합니다 Swift Charts는 이러한 기본 기능을 자동으로 처리합니다 따라서 여러분은 누구나 쉽게 데이터에 접근할 수 있도록 차트 제작에만 집중할 수 있죠 이를 위해 Swift Charts는 SwiftUI와 동일한 느낌의 선언적 구문을 제공합니다 여러분이 차트에 넣고 싶은 것을 짧은 코드로 지정하면 Swift Charts가 즉시 자동으로 멋진 차트를 생성합니다 그리고 Swift Charts는 풍부한 사용자 지정 옵션도 제공합니다 여러분의 앱과 어울리도록 차트를 디자인할 수 있죠 이번 세션을 통해 Swift Charts를 더욱 깊이 이해해 보겠습니다 가장 먼저 다룰 것은 선언적 구문의 기본 빌딩 블록입니다 마크와 마크의 구성이죠 차트를 활용하는 Apple 제품의 예시입니다 보시다시피 광범위한 데이터와 차트 유형, 스타일이 적용됐습니다 기본적으로 Swift Charts는 차트 유형마다 미리 제작된 요소를 제공하는 대신 구성 요소별로 차트를 생성합니다 제공하는 소수의 기본 빌딩 블록을 다양한 방식으로 결합함으로써 광범위한 유형의 차트를 만들 수 있죠 직접 살펴보겠습니다 그러려면 예시가 필요한데요 저희 팀원들은 팬케이크를 좋아합니다 그래서 다양한 팬케이크를 파는 푸드트럭의 주문을 추적하는 앱을 만들었죠 지난 30일간 판매된 팬케이크의 숫자를 종류별로 분류한 차트입니다 보통 막대 차트라고 불리죠 Swift Charts에서는 여섯 개의 파란색 직사각형을 마크라고 간주합니다 마크는 데이터를 나타내는 그래픽 요소입니다 이것은 지난 30일간 카차파 판매량을 나타내는 막대 마크입니다 이 차트에는 여섯 개의 마크가 있으며 각각 팬케이크의 유형과 판매량을 나타냅니다 작성된 코드를 살펴보죠 이 SwiftUI 뷰를 보면 서술적 제목이 들어갑니다 '가장 많이 팔린 유형 카차파'와 빈 차트가 있죠 단일 차트를 표현할 때 가장 적절한 것이 이런 차트 유형입니다 SwiftUI 앱에 차트를 추가할 수도 있습니다 다른 뷰를 추가하는 것처럼요 나머지 이야기에서는 차트에 초점을 맞추겠습니다 차트에 마크를 추가할 수 있습니다 여기 카차파의 판매량을 보여 주는 단일 BarMark가 있습니다 단일 막대 마크가 있는 차트가 생성되죠 화면으로 보시는 것처럼 차트의 형태가 나머지 UI와 잘 어울립니다 기본 스타일도 훌륭하죠 예를 들어 수치를 X축에서 반올림했습니다 다른 이름과 판매량으로 새 막대 마크를 추가하면 두 번째 막대가 등장합니다 이것을 반복해서 막대를 늘릴 수 있죠 실제 앱에서는 프로그래밍 방식으로 마크를 생성할 것입니다 차트에 구조체나 튜플 배열을 제공하거나 ForEach를 사용해서 각 요소의 값으로 막대 마크를 만듭니다 지금처럼 차트에서 ForEach가 유일한 콘텐츠일 경우 데이터를 차트에 직접 입력할 수도 있습니다 여러 SwiftUI 수정자를 마크에 활용할 수 있습니다 예를 들어 .foregroundStyle 수정자로 막대의 색을 설정할 수 있죠 여기서는 명명된 색으로 설정했습니다 Xcode에 명명된 에셋으로 생성할 수 있죠 누구나 차트에 쉽게 접근할 수 있는 것이 핵심입니다 기본적으로 차트는 자동 생성된 접근성 요소와 함께 VoiceOver 사용자에게 전달됩니다 .accessibilityLabel과 .accessibilityValue 수정자로 사용자 지정이 가능하죠 예를 들어 여기서는 레이블을 팬케이크 이름으로 설정하고 판매량 값은 '판매'로 설정합니다 VoiceOver 사용자에게 맞춤형 경험을 줄 수 있죠 카차파, 916 판매 인제라, 850 판매 크레프, 802 판매 우리 앱은 팬케이크 판매량을 날짜에 따라 추적합니다 이 뷰는 지난 30일간 판매된 팬케이크 수를 보여 줍니다 차트가 각각의 날에 대한 자세한 뷰를 제공하죠 이것으로 차트를 만들어 보죠 날짜와 판매량에 대한 데이터 배열입니다 day는 날짜가 시작될 때 Date 값을 표시합니다 BarMark로 데이터를 시각화했습니다 X는 날짜를 표시하고 unit 매개 변수는 Date 값이 달력의 날짜 기간을 나타낸다는 뜻입니다 Y축은 하루 판매량입니다 오른쪽에서 보시는 것처럼 며칠에 걸친 판매량이 막대 차트로 표시됩니다 다른 방법으로도 데이터를 시각화할 수 있습니다 꺾은선 차트를 만들어 보죠 유일한 차이점은 BarMark를 LineMark로 대체하는 것입니다 Swift 차트에서 선언적 구문을 사용하면 여러 유형의 차트를 쉽게 전환할 수 있습니다 이 차트는 시간에 따른 판매량을 보여 줍니다 그러나 푸드트럭 서비스는 두 도시에서 이뤄지므로 두 도시의 요일별 판매량을 비교함으로써 트럭이 가야 할 장소를 결정하려 합니다 월요일부터 일요일까지 판매량이 선으로 표시됩니다 각각의 선은 도시를 나타내죠 어떻게 만들었는지 살펴보죠 이제 두 도시의 데이터가 튜플 배열로 정의돼 있다고 가정한다면 각각 도시의 이름과 요일별 판매량이 들어 있습니다 시리즈 데이터를 반복하는 ForEach로 이전에 사용했던 꺾은선 차트를 만들 수 있죠 두 도시를 구분하기 위해 .foregroundStyle(by:) 수정자로 두 꺾은선의 스타일을 지정합니다 Swift Charts가 자동으로 두 가지 색을 골라서 두 도시의 선을 그렸습니다 그리고 각 색상이 의미하는 범례를 추가합니다 기본 색상은 구별하기 쉽도록 시스템 색상으로 선택됩니다 색맹인 사용자도 차트를 쉽게 읽을 수 있으려면 선에 기호를 추가해서 분명한 차이점을 줘야 합니다 그러려면 도시 데이터에 .symbol(by:)를 추가하세요 마지막으로 선이 부드러워 보이도록 보간법을 이용해서 꺾은선을 곡선으로 표현합니다 두 개의 시리즈 꺾은선 차트를 표시했지만 다시 막대 마크로 형태를 바꿔 보겠습니다 마크 유형을 BarMark로 바꾸고 막대 차트와 관련 없는 수정자를 제거하면 적층형 막대 차트가 표시됩니다 막대는 자동으로 적층됩니다 이제 막대그래프가 달마다 두 개이기 때문이죠 적층형 막대 차트로 두 도시의 총판매량을 쉽게 파악할 수 있지만 두 도시를 비교하기에는 절절하지 않습니다 쉽게 비교하기 위해서 .position(by:) 수정자로 그룹형 막대 차트를 만듭니다 지금까지 막대 마크와 꺾은선 마크를 살펴봤습니다 Swift Charts는 다른 마크 유형도 지원합니다 점, 영역, 괘선 및 직사각형 마크 등이 있죠 이러한 마크를 결합하면 복잡한 차트가 탄생합니다 예시를 살펴보죠 우선 월별 평균 일일 판매량을 보여 주는 꺾은선 차트입니다 평균은 유용한 정보지만 일일 판매량의 최소와 최대를 통해 양극단의 값을 알고 싶습니다 먼저 이 값을 데이터에 추가합니다 데이터 배열의 각 요소에 daily min과 daily max를 입력합니다 최솟값과 최댓값을 AreaMark로 시각화합니다 X축은 달을 의미하고 Y축은 일일 최솟값에서 시작해 일일 최댓값에서 끝납니다 이렇게 생성된 차트는 선으로 평균값을 보여 주고 선 주위에 영역을 그려서 최솟값과 최댓값을 표시합니다 이런 데이터를 시각화하는 방법은 이 외에도 다양합니다 다른 마크 유형으로 전환해서 다른 디자인 옵션을 탐색해 보죠 예를 들어 여기에서는 BarMark를 사용했습니다 하지만 선과 막대는 잘 어울리지 않는군요 RectangleMark로 바꿔 보죠 높이는 2입니다 RectangleMark는 막대 내부에 수평선으로 평균을 표시합니다 마크의 너비도 조절할 수 있습니다 예를 들어 여기에서는 너비를 0.6으로 설정합니다 이것으로 막대와 사각형의 너비가 한 달 너비의 60%로 줄었습니다 화면으로 확인할 수 있죠 마지막으로 달마다 일일 평균 판매량을 표시하는 옵션을 만들어 보겠습니다 우선 전경 스타일을 회색으로 어둡게 표시하겠습니다 다음에는 Y축에 평균값을 보여 주도록 ForEach에 RuleMark를 추가합니다 이것으로 수평 룰이 추가됩니다 이 룰이 연간 평균을 보여 준다는 것을 표시하고자 .annotation 수정자를 이용해서 규칙에 주석을 추가합니다 RuleMark 위에 선행 정렬과 함께 텍스트 레이블이 추가됩니다 지금까지 몇 가지 예시를 통해 구성 별로 차트를 작성해 봤습니다 이러한 기본 마크를 사용하고 결합하는 방법은 아주 다양합니다 박스 플롯, 멀티 시리즈 꺽은선 차트, 인구 피라미드 레인지 플롯, 스트림 그래프 멀티 시리즈 산점도 히트 맵, 또는 벡터장 필드도 그릴 수 있죠 Swift Charts로 만들 수 있는 다양한 차트의 일부 예시에 불과합니다 다음 주제로 넘어가죠 마크 속성을 이용한 데이터 그리기입니다 Swift Charts는 세 가지 주요 데이터를 지원합니다 양적, 명목적, 시간적 데이터죠 양적 데이터는 수치입니다 예를 들어 상품의 판매량 및 방의 온도와 상품의 가격 등입니다 Swift Charts는 Swift 수치 유형을 Int, Float, Double 등 양적 데이터로 다룹니다 명목적 데이터 또는 범주형 데이터는 개별적 범주 혹은 그룹을 나타냅니다 예를 들어 사람의 이름이나 대륙, 상품의 유형 등이죠 String 또는 커스텀 String 값 Enum을 명목적 데이터로 사용할 수 있습니다 시간 데이터는 특정 시각이나 시간의 간격을 나타냅니다 예를 들어 특정 하루의 길이나 정확한 거래 시각 등이죠 Swift Charts는 Date를 시간 데이터로 다룹니다 차트는 판매량 값 등 추상적인 데이터를 마크의 속성으로 변환합니다 X축과 Y축 및 전경 스타일로 차트를 그리는 BarMark를 살펴보죠 이 예시에서는 X축에 양적 유형인 판매량 값을 넣고 명목적 유형인 이름을 Y축에 넣었습니다 그 결과물인 수평 막대 차트는 X축에 판매량 Y축에 이름이 표시됩니다 이름과 판매량을 바꾸면 이름이 X축으로 가고 판매량이 Y축으로 가면서 수직 막대 차트가 나오죠 보시는 것처럼 BarMark의 동작은 X축과 Y축 속성으로 표시된 데이터 유형에 따라 달라집니다 막대의 방향은 양적 속성의 위치에 따라 달라지죠 다음은 데이터 표시에 사용된 세 가지 속성을 모두 사용한 차트입니다 여기서는 시간 데이터인 요일을 X축에 넣고 판매량을 Y축에 넣고 전경 스타일에 도시를 설정합니다 결과물은 적층형 막대 차트입니다 X축은 요일, Y축은 판매량 막대의 색은 도시를 나타내죠 Swift Charts의 마크 유형은 여섯 가지입니다 여섯 가지 마크 속성으로 데이터를 그릴 수 있죠 데이터의 유형도 세 가지라는 것을 잊지 마세요 배열할 수 있는 조합이 아주 방대하죠 덕분에 Swift Charts는 적은 수의 기본 빌딩 블록으로 폭넓은 차트 디자인을 지원할 수 있습니다 마크 속성을 이용해서 데이터를 그리면 예를 들어 Y축에 판매량을 놓으면 Swift Charts는 매핑을 생성해서 추상적 데이터를 적절한 속성값으로 변환합니다 이 경우에는 판매량 값을 화면 공간의 Y 좌표로 변환하죠 여기서 사용하는 '스케일'이란 용어는 판매량 같은 추상 데이터에서 Y 위치 같은 속성을 표시하는 매핑을 가리킵니다 간단히 말해서 스케일이란 데이터값을 가져와 속성값을 반환하는 함수입니다 예를 들어 다음은 판매량을 가져와서 막대의 Y 위치를 반환하는 yScale 함수입니다 '스케일'이라는 이름이 붙은 이유는 위치 속성에서 입력값을 적절한 화면 좌표로 변환할 때 몇 가지 비율을 이용해 크기를 조정하기 때문입니다 마크 속성으로 데이터를 그릴 때 스케일이 생성되며 데이터를 해당 마크 속성으로 변환합니다 예를 들어 이 차트에는 세 개의 스케일이 있습니다 각각의 요일을 X로 판매량을 Y로 도시를 전경 스타일로 변환하죠 기본적으로 Swift Charts는 데이터에서 자동으로 스케일을 추정해서 즉시 멋진 차트를 완성합니다 스케일 수정자를 이용하면 차트의 스케일을 구성할 수 있죠 예시를 살펴보겠습니다 이 예시에서 Y 스케일은 0부터 150까지 자동으로 선택됩니다 그러나 현재 판매량이 어떻든 Y 스케일이 늘 일정하도록 Y 스케일을 고정해 보죠 Y 스케일을 변경해서 늘 0에서 시작해 200에서 끝나도록 고치겠습니다 이를 위해 .chartYScale 수정자로 스케일의 영역을 0부터 200으로 설정합니다 이제 보시다시피 축이 0부터 200으로 바뀝니다 마찬가지로 두 도시를 전경 스타일로 그리려면 .chartForegroundStyleScale 수정자를 이용하십시오 두 도시의 색이 바뀌었습니다 지금까지 마크를 구성하고 마크 속성으로 데이터를 그리는 법을 배웠습니다 이제 Swift Charts가 제공하는 다양한 사용자 정의 옵션을 살펴보겠습니다 차트는 축과 범례 및 플롯 영역으로 구성됩니다 축과 범례로 차트를 해석할 수 있죠 플롯 영역은 두 축 사이의 공간입니다 우리가 마크로 데이터를 그리는 공간이죠 Swift Charts는 모든 요소를 사용자가 지정할 수 있습니다 우선 몇 가지 예시로 축과 범례를 지정해 보겠습니다 월별 총판매량을 보여 주는 차트입니다 사용자가 지정하지 않으면 Swift Charts는 반올림된 값으로 기본 축을 생성합니다 지금 X축에는 분기별 레이블이 표시됩니다 매달 한 글자만 표시하도록 레이블을 변경해 보겠습니다 우선 .chartXAxis 수정자를 추가해서 X축을 수정하고 AxisMarks로 콘텐츠를 바꿉니다 매개변수 없는 AxisMark는 기본 축을 재생성합니다 축 값부터 바꿔 보죠 한 달 간격으로 표시해야 하므로 표준 라이브러리의 stride 함수와 유사한 stride(by:)를 사용합니다 이제 매달 레이블이 붙습니다 하지만 보시다시피 기본 레이블은 너무 난잡하게 느껴집니다 어떤 레이블은 공간이 부족해서 글자가 잘렸습니다 단일 문자 형식을 사용하도록 레이블을 바꿔 보겠습니다 이를 위해 개별 구성 요소에서 AxisMarks를 만들고 AxisGridLine, AxisTick AxisValueLabel을 넣습니다 달 이름을 좁게 표시하도록 레이블 형식을 설정해 보죠 이제 한 달에 한 글자만 표시합니다 값 매개변수가 결과 빌더를 통과하면 현재의 축 값 정보를 제공합니다 이를 이용하면 조건데 따라 축 마크의 존재와 스타일을 결정할 수 있죠 예를 들어 이 조건문은 날짜 값이 각 분기의 첫 달인지 확인합니다 그렇다면 분기별 첫 달을 다른 전경 스타일로 강조할 수 있죠 아니라면 눈금이나 레이블 없이 격자 선만 표시합니다 이제 분기를 표시하고 있으므로 분기 스타일로 형식을 변경해 봅시다 방금 수행한 사용자 지정으로 분기 데이터를 보여 주는 더 분명한 X축을 만들었습니다 서브그리드 선으로 달을 표시하죠 축 마크는 값 외에도 다양한 속성이 있어서 일반적인 모양과 스타일을 설정할 수 있습니다 이제 Y축 형태를 바꿔서 기본 최댓값 대신 차트에서 가장 높은 선행 값을 표시하겠습니다 위치 매개변수를 선행 값으로 설정해서 상한선을 바꿉니다 Swift Charts가 제공하는 축 마크의 기본 프리셋은 시각화하는 데이터의 유형과 축에 따라 생성됩니다 프리셋 매개변수를 사용해서 프리셋을 무시할 수 있습니다 여기에서는 .extended 프리셋으로 나머지 UI와 어울리도록 Y축을 설정했습니다 축이 보이지 않는 차트도 만들 수 있습니다 예를 들어 최대 판매량 차트는 간략한 개요를 보는 용도이므로 자세한 축이 필요하지 않습니다 차트 축 수정자에 .hidden을 넣으면 축을 숨길 수 있죠 범례 구성은 축과 유사합니다 예를 들어 가장 잘 팔린 날과 장소를 강조한 이 차트에서는 불투명한 색으로 최고의 도시를 강조했습니다 자동 생성된 범례는 감출 수 있습니다 .chartLegend 수정자를 추가하고 매개변수로 .hidden을 넣으십시오 플롯 영역으로 넘어가죠 .chartPlotStyle 수정자를 사용하여 차트의 플롯 영역을 구성할 수 있습니다 후행 클로저에서는 기존 플롯 영역을 받아서 수정된 플롯 영역을 반환하는 함수를 작성합니다 몇 가지 예시를 보죠 플롯 영역의 크기나 종횡비를 정확히 지정하고 싶을 때도 있을 겁니다 예를 들어 여기에서는 차트의 범주 수에 따라 플롯 영역의 높이가 달라지길 원합니다 .frame 수정자를 플롯 영역에 적용하고 수정된 플롯 영역을 반환하는 방법이 있죠 플롯 영역의 높이가 설정될 것입니다 수정자로 특별한 시각 효과도 만들 수 있습니다 예를 들어 이 다크 모드 차트에서는 .background 수정자로 분홍색 배경을 넣고 차트가 잘 보이도록 불투명도를 0.2로 설정한 뒤 같은 분홍색으로 1포인트 두께 테두리를 둘렀죠 차트에 독특한 시각 효과가 탄생했습니다 앞서 언급한 스케일은 X나 Y 같은 마크 속성에 데이터값을 매핑하는 함수입니다 Swift Charts는 ChartProxy를 이용해서 차트의 X와 Y 스케일에 접근할 수 있습니다 ChartProxy의 position(for:) 메소드로 주어진 데이터값의 위치를 얻을 수 있죠 또는 value(at:) 메소드를 사용해서 주어진 위치의 데이터값을 구할 수 있습니다 이를 통해 차트와 다른 뷰를 조정할 수 있습니다 예시를 살펴보죠 인터랙티브 브러싱 뷰를 만들려 합니다 차트에서 드래그 제스처로 간격을 선택할 수 있고 그 간격은 세부 정보 뷰에서 행을 필터링하는 데 사용됩니다 .chartOverlay 또는 .chartBackground 수정자에서 차트 프록시 개체를 구할 수 있습니다 두 수정자는 SwiftUI의 오버레이 및 배경 수정자와 유사하지만 차트 프록시를 제공합니다 이 예시를 만들기에 앞서 이전처럼 기본 차트를 정의하고 시작하겠습니다 그런 다음 차트 프록시를 제공하는 .chartOverlay 수정자를 추가합니다 내부에는 오버레이 뷰의 지오메트리에 접근할 수 있는 GeometryReader가 들어갑니다 다음으로 SwiftUI의 DragGesture에 응답하는 DragGesture가 들어갑니다 드래그 제스처가 입력되면 우선 X 좌표가 차트의 플롯 영역에서 X의 시작점과 현재 위치를 찾습니다 제스처가 제공한 위치에서 플롯 영역의 원점을 빼는 식으로 수행되죠 이런 좌표가 있으면 차트 프록시를 이용하여 해당 날짜 값을 구할 수 있습니다 이제 마지막으로 SwiftUI가 현재 날짜 간격을 유지하도록 설정해야 합니다 범위 스테이트로 직사각형 마크를 정의하면 현재 선택한 날짜 범위를 시각화할 수 있습니다 이 스테이트는 여러분의 앱에서 다른 부분도 제어할 수 있습니다 예를 들어 차트 아래에 나오는 상세 내용을 필터링할 수 있죠 차트 프록시의 작동 방식을 보여 주는 간단한 예시입니다 이것으로 흥미로운 기능을 만들 수 있습니다 예를 들어 이 인터랙티브 차트는 막대사탕처럼 생긴 오버레이로 선택한 날과 판매량 값을 보여 줍니다 이번 세션에서는 마크를 구성하며 차트를 만드는 법을 배웠고 마크 속성으로 데이터를 그리는 법과 사용자 지정 방법을 알아봤습니다 다음 디자인 세션으로 넘어가면 차트로 훌륭한 앱 경험을 디자인하는 방법과 효율적인 차트를 만드는 법을 배울 겁니다 Swift Charts의 데이터 시각화를 좋아하시리라 믿습니다 시청해 주셔서 감사합니다 ♪
-
-
3:48 - Top style chart
import SwiftUI import Charts struct TopStyleChart: View { let data = [ (name: "Cachapa", sales: 916), (name: "Injera", sales: 850), (name: "Crêpe", sales: 802), (name: "Jian Bing", sales: 753), (name: "Dosa", sales: 654), (name: "American", sales: 618) ] var body: some View { Chart(data, id: \.name) { BarMark( x: .value("Sales", $0.sales), y: .value("Name", $0.name) ) // Set the foreground style of the bars. .foregroundStyle(.pink) // Customize the accessibility label and value. .accessibilityLabel($0.name) .accessibilityValue("\($0.sales) sold") } } }
-
5:12 - Daily sales chart
struct DailySalesChart: View { var body: some View { Chart { ForEach(dailySales, id: \.day) { // Try change to LineMark. BarMark( x: .value("Day", $0.day, unit: .day), y: .value("Sales", $0.sales) ) } } } let dailySales: [(day: Date, sales: Int)] = [ (day: date(year: 2022, month: 5, day: 8), sales: 168), (day: date(year: 2022, month: 5, day: 9), sales: 117), (day: date(year: 2022, month: 5, day: 10), sales: 106), (day: date(year: 2022, month: 5, day: 11), sales: 119), (day: date(year: 2022, month: 5, day: 12), sales: 109), (day: date(year: 2022, month: 5, day: 13), sales: 104), (day: date(year: 2022, month: 5, day: 14), sales: 196), (day: date(year: 2022, month: 5, day: 15), sales: 172), (day: date(year: 2022, month: 5, day: 16), sales: 122), (day: date(year: 2022, month: 5, day: 17), sales: 115), (day: date(year: 2022, month: 5, day: 18), sales: 138), (day: date(year: 2022, month: 5, day: 19), sales: 110), (day: date(year: 2022, month: 5, day: 20), sales: 106), (day: date(year: 2022, month: 5, day: 21), sales: 187), (day: date(year: 2022, month: 5, day: 22), sales: 187), (day: date(year: 2022, month: 5, day: 23), sales: 119), (day: date(year: 2022, month: 5, day: 24), sales: 160), (day: date(year: 2022, month: 5, day: 25), sales: 144), (day: date(year: 2022, month: 5, day: 26), sales: 152), (day: date(year: 2022, month: 5, day: 27), sales: 148), (day: date(year: 2022, month: 5, day: 28), sales: 240), (day: date(year: 2022, month: 5, day: 29), sales: 242), (day: date(year: 2022, month: 5, day: 30), sales: 173), (day: date(year: 2022, month: 5, day: 31), sales: 143), (day: date(year: 2022, month: 6, day: 1), sales: 137), (day: date(year: 2022, month: 6, day: 2), sales: 123), (day: date(year: 2022, month: 6, day: 3), sales: 146), (day: date(year: 2022, month: 6, day: 4), sales: 214), (day: date(year: 2022, month: 6, day: 5), sales: 250), (day: date(year: 2022, month: 6, day: 6), sales: 146) ] } func date(year: Int, month: Int, day: Int = 1) -> Date { Calendar.current.date(from: .init(year: year, month: month, day: day)) ?? Date() }
-
6:16 - Sales by location with line mark
struct LocationsChart: View { var body: some View { Chart { ForEach(seriesData, id: \.city) { series in ForEach(series.data, id: \.weekday) { LineMark( x: .value("Weekday", $0.weekday, unit: .day), y: .value("Sales", $0.sales) ) } .foregroundStyle(by: .value("City", series.city)) .symbol(by: .value("City", series.city)) .interpolationMethod(.catmullRom) } } } let seriesData = [ ( city: "Cupertino", data: [ (weekday: date(year: 2022, month: 5, day: 2), sales: 54), (weekday: date(year: 2022, month: 5, day: 3), sales: 42), (weekday: date(year: 2022, month: 5, day: 4), sales: 88), (weekday: date(year: 2022, month: 5, day: 5), sales: 49), (weekday: date(year: 2022, month: 5, day: 6), sales: 42), (weekday: date(year: 2022, month: 5, day: 7), sales: 125), (weekday: date(year: 2022, month: 5, day: 8), sales: 67) ] ), ( city: "San Francisco", data: [ (weekday: date(year: 2022, month: 5, day: 2), sales: 81), (weekday: date(year: 2022, month: 5, day: 3), sales: 90), (weekday: date(year: 2022, month: 5, day: 4), sales: 52), (weekday: date(year: 2022, month: 5, day: 5), sales: 72), (weekday: date(year: 2022, month: 5, day: 6), sales: 84), (weekday: date(year: 2022, month: 5, day: 7), sales: 84), (weekday: date(year: 2022, month: 5, day: 8), sales: 137) ] ) ] } func date(year: Int, month: Int, day: Int = 1) -> Date { Calendar.current.date(from: DateComponents(year: year, month: month, day: day)) ?? Date() }
-
7:19 - Sales by location with bar mark
struct LocationsChart: View { var body: some View { Chart { ForEach(seriesData, id: \.city) { series in ForEach(series.data, id: \.weekday) { BarMark( x: .value("Weekday", $0.weekday, unit: .day), y: .value("Sales", $0.sales) ) } .foregroundStyle(by: .value("City", series.city)) .position(by: .value("City", series.city)) } } } let seriesData = [ ( city: "Cupertino", data: [ (weekday: date(year: 2022, month: 5, day: 2), sales: 54), (weekday: date(year: 2022, month: 5, day: 3), sales: 42), (weekday: date(year: 2022, month: 5, day: 4), sales: 88), (weekday: date(year: 2022, month: 5, day: 5), sales: 49), (weekday: date(year: 2022, month: 5, day: 6), sales: 42), (weekday: date(year: 2022, month: 5, day: 7), sales: 125), (weekday: date(year: 2022, month: 5, day: 8), sales: 67) ] ), ( city: "San Francisco", data: [ (weekday: date(year: 2022, month: 5, day: 2), sales: 81), (weekday: date(year: 2022, month: 5, day: 3), sales: 90), (weekday: date(year: 2022, month: 5, day: 4), sales: 52), (weekday: date(year: 2022, month: 5, day: 5), sales: 72), (weekday: date(year: 2022, month: 5, day: 6), sales: 84), (weekday: date(year: 2022, month: 5, day: 7), sales: 84), (weekday: date(year: 2022, month: 5, day: 8), sales: 137) ] ) ] } func date(year: Int, month: Int, day: Int = 1) -> Date { Calendar.current.date(from: DateComponents(year: year, month: month, day: day)) ?? Date() }
-
8:02 - Monthly sales with line and area marks
struct MonthlySalesChart: View { var body: some View { Chart { ForEach(data, id: \.month) { AreaMark( x: .value("Month", $0.month, unit: .month), yStart: .value("Daily Min", $0.dailyMin), yEnd: .value("Daily Max", $0.dailyMax) ) .opacity(0.3) LineMark( x: .value("Month", $0.month, unit: .month), y: .value("Daily Average", $0.dailyAverage) ) } } } let data = [ (month: date(year: 2021, month: 7), sales: 3952, dailyAverage: 127, dailyMin: 95, dailyMax: 194), (month: date(year: 2021, month: 8), sales: 4044, dailyAverage: 130, dailyMin: 96, dailyMax: 189), (month: date(year: 2021, month: 9), sales: 3930, dailyAverage: 131, dailyMin: 101, dailyMax: 184), (month: date(year: 2021, month: 10), sales: 4217, dailyAverage: 136, dailyMin: 96, dailyMax: 193), (month: date(year: 2021, month: 11), sales: 4006, dailyAverage: 134, dailyMin: 104, dailyMax: 202), (month: date(year: 2021, month: 12), sales: 3994, dailyAverage: 129, dailyMin: 96, dailyMax: 190), (month: date(year: 2022, month: 1), sales: 4202, dailyAverage: 136, dailyMin: 96, dailyMax: 203), (month: date(year: 2022, month: 2), sales: 3749, dailyAverage: 134, dailyMin: 98, dailyMax: 200), (month: date(year: 2022, month: 3), sales: 4329, dailyAverage: 140, dailyMin: 104, dailyMax: 218), (month: date(year: 2022, month: 4), sales: 4084, dailyAverage: 136, dailyMin: 93, dailyMax: 221), (month: date(year: 2022, month: 5), sales: 4559, dailyAverage: 147, dailyMin: 104, dailyMax: 242), (month: date(year: 2022, month: 6), sales: 1023, dailyAverage: 170, dailyMin: 120, dailyMax: 250) ] } func date(year: Int, month: Int, day: Int = 1) -> Date { Calendar.current.date(from: DateComponents(year: year, month: month, day: day)) ?? Date() }
-
8:46 - Monthly sales with bar and rectangle marks
struct MonthlySalesChart: View { var body: some View { Chart { ForEach(data, id: \.month) { BarMark( x: .value("Month", $0.month, unit: .month), yStart: .value("Daily Min", $0.dailyMin), yEnd: .value("Daily Max", $0.dailyMax), width: .ratio(0.6) ) .opacity(0.3) RectangleMark( x: .value("Month", $0.month, unit: .month), y: .value("Daily Average", $0.dailyAverage), width: .ratio(0.6), height: 2 ) } } } let data = [ (month: date(year: 2021, month: 7), sales: 3952, dailyAverage: 127, dailyMin: 95, dailyMax: 194), (month: date(year: 2021, month: 8), sales: 4044, dailyAverage: 130, dailyMin: 96, dailyMax: 189), (month: date(year: 2021, month: 9), sales: 3930, dailyAverage: 131, dailyMin: 101, dailyMax: 184), (month: date(year: 2021, month: 10), sales: 4217, dailyAverage: 136, dailyMin: 96, dailyMax: 193), (month: date(year: 2021, month: 11), sales: 4006, dailyAverage: 134, dailyMin: 104, dailyMax: 202), (month: date(year: 2021, month: 12), sales: 3994, dailyAverage: 129, dailyMin: 96, dailyMax: 190), (month: date(year: 2022, month: 1), sales: 4202, dailyAverage: 136, dailyMin: 96, dailyMax: 203), (month: date(year: 2022, month: 2), sales: 3749, dailyAverage: 134, dailyMin: 98, dailyMax: 200), (month: date(year: 2022, month: 3), sales: 4329, dailyAverage: 140, dailyMin: 104, dailyMax: 218), (month: date(year: 2022, month: 4), sales: 4084, dailyAverage: 136, dailyMin: 93, dailyMax: 221), (month: date(year: 2022, month: 5), sales: 4559, dailyAverage: 147, dailyMin: 104, dailyMax: 242), (month: date(year: 2022, month: 6), sales: 1023, dailyAverage: 170, dailyMin: 120, dailyMax: 250) ] } func date(year: Int, month: Int, day: Int = 1) -> Date { Calendar.current.date(from: DateComponents(year: year, month: month, day: day)) ?? Date() }
-
9:19 - Monthly sales with average line and annotation
struct MonthlySalesChart: View { var body: some View { Chart { ForEach(data, id: \.month) { BarMark( x: .value("Month", $0.month, unit: .month), yStart: .value("Daily Min", $0.dailyMin), yEnd: .value("Daily Max", $0.dailyMax), width: .ratio(0.6) ) .opacity(0.3) RectangleMark( x: .value("Month", $0.month, unit: .month), y: .value("Daily Average", $0.dailyAverage), width: .ratio(0.6), height: 2 ) } .foregroundStyle(.gray.opacity(0.5)) RuleMark( y: .value("Average", averageValue) ) .lineStyle(StrokeStyle(lineWidth: 3)) .annotation(position: .top, alignment: .leading) { Text("Average: \(averageValue, format: .number)") .font(.headline) .foregroundStyle(.blue) } } } let data = [ (month: date(year: 2021, month: 7), sales: 3952, dailyAverage: 127, dailyMin: 95, dailyMax: 194), (month: date(year: 2021, month: 8), sales: 4044, dailyAverage: 130, dailyMin: 96, dailyMax: 189), (month: date(year: 2021, month: 9), sales: 3930, dailyAverage: 131, dailyMin: 101, dailyMax: 184), (month: date(year: 2021, month: 10), sales: 4217, dailyAverage: 136, dailyMin: 96, dailyMax: 193), (month: date(year: 2021, month: 11), sales: 4006, dailyAverage: 134, dailyMin: 104, dailyMax: 202), (month: date(year: 2021, month: 12), sales: 3994, dailyAverage: 129, dailyMin: 96, dailyMax: 190), (month: date(year: 2022, month: 1), sales: 4202, dailyAverage: 136, dailyMin: 96, dailyMax: 203), (month: date(year: 2022, month: 2), sales: 3749, dailyAverage: 134, dailyMin: 98, dailyMax: 200), (month: date(year: 2022, month: 3), sales: 4329, dailyAverage: 140, dailyMin: 104, dailyMax: 218), (month: date(year: 2022, month: 4), sales: 4084, dailyAverage: 136, dailyMin: 93, dailyMax: 221), (month: date(year: 2022, month: 5), sales: 4559, dailyAverage: 147, dailyMin: 104, dailyMax: 242), (month: date(year: 2022, month: 6), sales: 1023, dailyAverage: 170, dailyMin: 120, dailyMax: 250) ] let averageValue = 137 } func date(year: Int, month: Int, day: Int = 1) -> Date { Calendar.current.date(from: DateComponents(year: year, month: month, day: day)) ?? Date() }
-
13:54 - Chart with custom scales for Y and foreground style
struct LocationsChart: View { var body: some View { Chart { ForEach(seriesData, id: \.city) { series in ForEach(series.data, id: \.weekday) { LineMark( x: .value("Weekday", $0.weekday, unit: .day), y: .value("Sales", $0.sales) ) } .foregroundStyle(by: .value("City", series.city)) .symbol(by: .value("City", series.city)) .interpolationMethod(.catmullRom) } } .chartYScale(domain: 0 ... 200) .chartForegroundStyleScale([ "San Francisco": .orange, "Cupertino": .pink ]) } let seriesData = [ ( city: "Cupertino", data: [ (weekday: date(year: 2022, month: 5, day: 2), sales: 54), (weekday: date(year: 2022, month: 5, day: 3), sales: 42), (weekday: date(year: 2022, month: 5, day: 4), sales: 88), (weekday: date(year: 2022, month: 5, day: 5), sales: 49), (weekday: date(year: 2022, month: 5, day: 6), sales: 42), (weekday: date(year: 2022, month: 5, day: 7), sales: 125), (weekday: date(year: 2022, month: 5, day: 8), sales: 67) ] ), ( city: "San Francisco", data: [ (weekday: date(year: 2022, month: 5, day: 2), sales: 81), (weekday: date(year: 2022, month: 5, day: 3), sales: 90), (weekday: date(year: 2022, month: 5, day: 4), sales: 52), (weekday: date(year: 2022, month: 5, day: 5), sales: 72), (weekday: date(year: 2022, month: 5, day: 6), sales: 84), (weekday: date(year: 2022, month: 5, day: 7), sales: 84), (weekday: date(year: 2022, month: 5, day: 8), sales: 137) ] ) ] } func date(year: Int, month: Int, day: Int = 1) -> Date { Calendar.current.date(from: DateComponents(year: year, month: month, day: day)) ?? Date() }
-
15:16 - Chart with custom X axis
struct MonthlySalesChart: View { var body: some View { Chart(data, id: \.month) { BarMark( x: .value("Month", $0.month, unit: .month), y: .value("Sales", $0.sales) ) } .chartXAxis { AxisMarks( values: .stride(by: .month) ) { value in AxisGridLine() AxisTick() AxisValueLabel( format: .dateTime.month(.narrow) ) } } } let data = [ (month: date(year: 2021, month: 7), sales: 3952), (month: date(year: 2021, month: 8), sales: 4044), (month: date(year: 2021, month: 9), sales: 3930), (month: date(year: 2021, month: 10), sales: 4217), (month: date(year: 2021, month: 11), sales: 4006), (month: date(year: 2021, month: 12), sales: 3994), (month: date(year: 2022, month: 1), sales: 4202), (month: date(year: 2022, month: 2), sales: 3749), (month: date(year: 2022, month: 3), sales: 4329), (month: date(year: 2022, month: 4), sales: 4084), (month: date(year: 2022, month: 5), sales: 4559), (month: date(year: 2022, month: 6), sales: 1023) ] let averageValue = 137 } func date(year: Int, month: Int, day: Int = 1) -> Date { Calendar.current.date(from: DateComponents(year: year, month: month, day: day)) ?? Date() }
-
16:17 - Chart with custom X axis and conditional content for axis marks
struct MonthlySalesChart: View { var body: some View { Chart(data, id: \.month) { BarMark( x: .value("Month", $0.month, unit: .month), y: .value("Sales", $0.sales) ) } .chartXAxis { AxisMarks(values: .stride(by: .month)) { value in if value.as(Date.self)!.isFirstMonthOfQuarter { AxisGridLine().foregroundStyle(.black) AxisTick().foregroundStyle(.black) AxisValueLabel( format: .dateTime.month(.narrow) ) } else { AxisGridLine() } } } } let data = [ (month: date(year: 2021, month: 7), sales: 3952), (month: date(year: 2021, month: 8), sales: 4044), (month: date(year: 2021, month: 9), sales: 3930), (month: date(year: 2021, month: 10), sales: 4217), (month: date(year: 2021, month: 11), sales: 4006), (month: date(year: 2021, month: 12), sales: 3994), (month: date(year: 2022, month: 1), sales: 4202), (month: date(year: 2022, month: 2), sales: 3749), (month: date(year: 2022, month: 3), sales: 4329), (month: date(year: 2022, month: 4), sales: 4084), (month: date(year: 2022, month: 5), sales: 4559), (month: date(year: 2022, month: 6), sales: 1023) ] let averageValue = 137 } extension Date { var isFirstMonthOfQuarter: Bool { Calendar.current.component(.month, from: self) % 3 == 1 } } func date(year: Int, month: Int, day: Int = 1) -> Date { Calendar.current.date(from: DateComponents(year: year, month: month, day: day)) ?? Date() }
-
17:00 - Chart with custom Y axis
struct MonthlySalesChart: View { var body: some View { Chart(data, id: \.month) { BarMark( x: .value("Month", $0.month, unit: .month), y: .value("Sales", $0.sales) ) } .chartYAxis { AxisMarks( preset: .extended, position: .leading) } } let data = [ (month: date(year: 2021, month: 7), sales: 3952), (month: date(year: 2021, month: 8), sales: 4044), (month: date(year: 2021, month: 9), sales: 3930), (month: date(year: 2021, month: 10), sales: 4217), (month: date(year: 2021, month: 11), sales: 4006), (month: date(year: 2021, month: 12), sales: 3994), (month: date(year: 2022, month: 1), sales: 4202), (month: date(year: 2022, month: 2), sales: 3749), (month: date(year: 2022, month: 3), sales: 4329), (month: date(year: 2022, month: 4), sales: 4084), (month: date(year: 2022, month: 5), sales: 4559), (month: date(year: 2022, month: 6), sales: 1023) ] let averageValue = 137 } func date(year: Int, month: Int, day: Int = 1) -> Date { Calendar.current.date(from: DateComponents(year: year, month: month, day: day)) ?? Date() }
-
18:26 - Chart with plot area style
struct TopStyleChart: View { var body: some View { Chart(data, id: \.name) { BarMark( x: .value("Sales", $0.sales), y: .value("Name", $0.name) ) // Set the foreground style of the bars. .foregroundStyle(.pink) // Customize the accessibility label and value. .accessibilityLabel($0.name) .accessibilityValue("\($0.sales) sold") } .chartPlotStyle { plotArea in plotArea.frame(height: 60 * 6) .background(.pink.opacity(0.2)) .border(.pink, width: 1) } } let data = [ (name: "Cachapa", sales: 916), (name: "Injera", sales: 850), (name: "Crêpe", sales: 802), (name: "Jian Bing", sales: 753), (name: "Dosa", sales: 654), (name: "American", sales: 618) ] }
-
20:03 - Chart with brushing interaction
struct InteractiveBrushingChart: View { @State var range: (Date, Date)? = nil var body: some View { Chart { ForEach(data, id: \.day) { LineMark( x: .value("Month", $0.day, unit: .day), y: .value("Sales", $0.sales) ) .interpolationMethod(.catmullRom) .symbol(Circle().strokeBorder(lineWidth: 2)) } if let (start, end) = range { RectangleMark( xStart: .value("Selection Start", start), xEnd: .value("Selection End", end) ) .foregroundStyle(.gray.opacity(0.2)) } } .chartOverlay { proxy in GeometryReader { nthGeoItem in Rectangle().fill(.clear).contentShape(Rectangle()) .gesture(DragGesture() .onChanged { value in // Find the x-coordinates in the chart’s plot area. let xStart = value.startLocation.x - nthGeoItem[proxy.plotAreaFrame].origin.x let xCurrent = value.location.x - nthGeoItem[proxy.plotAreaFrame].origin.x // Find the date values at the x-coordinates. if let dateStart: Date = proxy.value(atX: xStart), let dateCurrent: Date = proxy.value(atX: xCurrent) { range = (dateStart, dateCurrent) } } .onEnded { _ in range = nil } // Clear the state on gesture end. ) } } } let data: [(day: Date, sales: Int)] = [ (day: date(year: 2022, month: 5, day: 8), sales: 168), (day: date(year: 2022, month: 5, day: 9), sales: 117), (day: date(year: 2022, month: 5, day: 10), sales: 106), (day: date(year: 2022, month: 5, day: 11), sales: 119), (day: date(year: 2022, month: 5, day: 12), sales: 109), (day: date(year: 2022, month: 5, day: 13), sales: 104), (day: date(year: 2022, month: 5, day: 14), sales: 196), (day: date(year: 2022, month: 5, day: 15), sales: 172), (day: date(year: 2022, month: 5, day: 16), sales: 122), (day: date(year: 2022, month: 5, day: 17), sales: 115), (day: date(year: 2022, month: 5, day: 18), sales: 138), (day: date(year: 2022, month: 5, day: 19), sales: 110), (day: date(year: 2022, month: 5, day: 20), sales: 106), (day: date(year: 2022, month: 5, day: 21), sales: 187), (day: date(year: 2022, month: 5, day: 22), sales: 187), (day: date(year: 2022, month: 5, day: 23), sales: 119), (day: date(year: 2022, month: 5, day: 24), sales: 160), (day: date(year: 2022, month: 5, day: 25), sales: 144), (day: date(year: 2022, month: 5, day: 26), sales: 152), (day: date(year: 2022, month: 5, day: 27), sales: 148), (day: date(year: 2022, month: 5, day: 28), sales: 240), (day: date(year: 2022, month: 5, day: 29), sales: 242), (day: date(year: 2022, month: 5, day: 30), sales: 173), (day: date(year: 2022, month: 5, day: 31), sales: 143), (day: date(year: 2022, month: 6, day: 1), sales: 137), (day: date(year: 2022, month: 6, day: 2), sales: 123), (day: date(year: 2022, month: 6, day: 3), sales: 146), (day: date(year: 2022, month: 6, day: 4), sales: 214), (day: date(year: 2022, month: 6, day: 5), sales: 250), (day: date(year: 2022, month: 6, day: 6), sales: 146) ] } func date(year: Int, month: Int, day: Int = 1) -> Date { Calendar.current.date(from: DateComponents(year: year, month: month, day: day)) ?? Date() }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.