Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Pathを活用してSwift Chartsの限界を超えろ / create-graph-using-path

Pathを活用してSwift Chartsの限界を超えろ / create-graph-using-path

Fuya Yamada

July 14, 2023
Tweet

Other Decks in Technology

Transcript

  1. PathΛ׆༻ͯ͠


    Swift ChartsͷݶքΛ௒͑Ζ
    גࣜձࣾZOZO


    ϒϥϯυιϦϡʔγϣϯ։ൃຊ෦ / ϑϩϯτΤϯυ෦


    WEAR iOSϒϩοΫ


    ࢁా ෨໵
    Copyright © ZOZO, Inc.

    View Slide

  2. © ZOZO, Inc.
    גࣜձࣾZOZO


    ϒϥϯυιϦϡʔγϣϯ։ൃຊ෦ / ϑϩϯτΤϯυ෦


    WEAR iOSϒϩοΫ
    ࢁా ෨໵ (Fuya Yamada)
    ελόʹߦͬͯɺۤΈʹ଱͑ͳ͕Β
    ҿΉίʔώʔ͕޷͖


    ʢຊ౰͸ίίΞ͕ྑ͍ʣ

    View Slide

  3. © ZOZO, Inc.
    ຊ೔࿩͢͜ͱ
    PathΛ׆༻ͯ͠


    Swift Chartsʹ͸ͳ͍άϥϑΛ࡞੒ͯ͠Έͨ
    ର৅


    Կ͔͠ΒSwiftUIΛѻ͏ਓ

    View Slide

  4. © ZOZO, Inc.
    ΞδΣϯμ
    ● Swift Chartsͷ঺հ


    ● Swift Chartsͷݶք


    ● PathΛ༻͍ͯݶքಥഁ͢Δํ๏


    ● Swift Chartsʹ͸ͳ͍άϥϑͷ࣮૷ྫ


    ● ·ͱΊ
    4

    View Slide

  5. © ZOZO, Inc.
    Swift Chartsͷ঺հ
    Swift Chartsͱ͸ʁ


    ○ iOS 16͔Βར༻Ͱ͖Δ SwiftUI ͷάϥϑඳըϥΠϒϥϦ


    ○ ๮ɺંΕઢɺࢄ෍ਤΛ͸͡Ίͱ͢Δ6छྨͷඳը͕Մೳ
    5

    View Slide

  6. © ZOZO, Inc.
    Swift Chartsͷ঺հ
    6
    ຌྫͷ௥Ճ ਫฏઢͷදࣔ
    ͜ΜͳΧελϚΠζ͕Ͱ͖·͢

    View Slide

  7. © ZOZO, Inc.
    Swift Chartsͷݶքʢ՝୊ʣ
    ԁάϥϑ΍Ϩʔμʔνϟʔτ͸ඇରԠ
    7
    Swift ChartsͰαϙʔτ͞Ε͍ͯͳ͍άϥϑ͸ಠࣗͰ࡞Δඞཁ͕͋Δ
    https://chachart.net/radar

    View Slide

  8. © ZOZO, Inc.
    PathΛ༻͍ͯݶքಥഁ͢Δํ๏
    PathΛ׆༻ͯ͠άϥϑΛ࡞੒


    Pathͱ͸ʁ


    ➔ ௚ઢɺۂઢͳͲΛ૊Έ߹Θͤͯෳࡶͳܗঢ়Λ࡞੒͢ΔͨΊͷߏ଄ମ


    ϝϦοτ


    ➔ දݱͷࣗ༝౓ͷߴ͞
    8
    Path { path in


    ...


    }
    ChartsͰ͸දݱͰ͖ͳ͔ͬͨϨΠΞ΢τΛ࣮ݱՄೳ

    View Slide

  9. © ZOZO, Inc.
    PathͷͰ͖Δ͜ͱ
    Path { path in


    path.move(to: CGPoint(x: 0, y: 0))


    path.addLine(to: CGPoint(x: 100, y: 0))


    }
    9
    path.closeSubpath() // ύεΛด͡Δ
    path.addArc(


    center: CGPoint(x: 100, y: 100),


    radius: 100,


    startAngle: .degrees(-90),


    endAngle: .degrees(0),


    clockwise: false


    )
    ࢝఺↓

    View Slide

  10. © ZOZO, Inc.
    ShapeϓϩτίϧΛར༻


    ඳըՄೳͳਤܗΛఆٛ͢ΔͨΊͷΠϯλʔϑΣʔε
    PathͰԁάϥϑΛ࣮૷
    10
    public protocol Shape : Animatable, View {


    func path(in rect: CGRect) -> Path


    }
    Animatable


    ೚ҙͷ஋͕มԽͨ࣌͠ɺ
    ΞχϝʔγϣϯΛద༻
    path(in:)ϝιου


    ༩͑ΒΕͨCGRect (ඳըྖҬ)಺
    ʹඳը͢ΔPath

    View Slide

  11. © ZOZO, Inc.
    PathͰԁάϥϑΛ࣮૷
    11
    ࡞Γ͍ͨ΋ͷ
    ԁހͷ࡞੒


    3ͭͷ૊Έ߹Θͤ
    ArcView

    View Slide

  12. © ZOZO, Inc.
    struct ArcView: Shape {


    var startAngle: Double


    var endAngle: Double


    var animatableData: Double {


    get { endAngle }


    set { endAngle = newValue }


    }


    func path(in rect: CGRect) -> Path {


    let rotationAdjustment = Angle(degrees: 90)


    let adjustedStartAngle = Angle(degrees: startAngle) - rotationAdjustment


    let adjustedEndAngle = Angle(degrees: endAngle) - rotationAdjustment


    return Path { p in


    p.addArc(


    center: CGPoint(x: rect.midX, y: rect.midY),


    radius: rect.width / 2,


    startAngle: adjustedStartAngle, endAngle: adjustedEndAngle,


    clockwise: false


    )


    p.addLine(to: CGPoint(x: rect.midX, y: rect.midY))


    p.closeSubpath()


    }


    }


    }
    ArcViewͷ࣮૷ - શମ૾
    12
    ArcView

    View Slide

  13. © ZOZO, Inc.
    struct ArcView: Shape {


    var startAngle: Double


    var endAngle: Double


    var animatableData: Double {


    get { endAngle }


    set { endAngle = newValue }


    }


    func path(in rect: CGRect) -> Path {


    let rotationAdjustment = Angle(degrees: 90)


    let adjustedStartAngle = Angle(degrees: startAngle) - rotationAdjustment


    let adjustedEndAngle = Angle(degrees: endAngle) - rotationAdjustment


    return Path { p in


    p.addArc(


    center: CGPoint(x: rect.midX, y: rect.midY),


    radius: rect.width / 2,


    startAngle: adjustedStartAngle, endAngle: adjustedEndAngle,


    clockwise: false


    )


    p.addLine(to: CGPoint(x: rect.midX, y: rect.midY))


    p.closeSubpath()


    }


    }


    }
    ArcViewͷ࣮૷
    AnimatableData

    ϓϩύςΟ


    e.g.


    endAngleʢऴྃ֯౓ʣΛ


    ࢦఆ͢ΔͱArcViewͷׂ߹͕
    ૿Ճ͢ΔΞχϝʔγϣϯ
    var animatableData: Double {


    get { endAngle }


    set { endAngle = newValue }


    }
    13

    View Slide

  14. © ZOZO, Inc.
    struct ArcView: Shape {


    var startAngle: Double


    var endAngle: Double


    var animatableData: Double {


    get { endAngle }


    set { endAngle = newValue }


    }


    func path(in rect: CGRect) -> Path {


    let rotationAdjustment = Angle(degrees: 90)


    let adjustedStartAngle = Angle(degrees: startAngle) - rotationAdjustment


    let adjustedEndAngle = Angle(degrees: endAngle) - rotationAdjustment


    return Path { p in


    p.addArc(


    center: CGPoint(x: rect.midX, y: rect.midY),


    radius: rect.width / 2,


    startAngle: adjustedStartAngle, endAngle: adjustedEndAngle,


    clockwise: false


    )


    p.addLine(to: CGPoint(x: rect.midX, y: rect.midY))


    p.closeSubpath()


    }


    }


    }


    ArcViewͷ࣮૷
    0౓Λ্෦ʹ͢ΔͨΊʹ


    ֯౓ௐ੔
    let rotationAdjustment = Angle(degrees: 90)


    let adjustedStartAngle = Angle(degrees: startAngle) - rotationAdjustment


    let adjustedEndAngle = Angle(degrees: endAngle) - rotationAdjustment
    14
    0° 0°

    View Slide

  15. © ZOZO, Inc.
    struct ArcView: Shape {


    var startAngle: Double


    var endAngle: Double


    var animatableData: Double {


    get { endAngle }


    set { endAngle = newValue }


    }


    func path(in rect: CGRect) -> Path {


    let rotationAdjustment = Angle(degrees: 90)


    let adjustedStartAngle = Angle(degrees: startAngle) - rotationAdjustment


    let adjustedEndAngle = Angle(degrees: endAngle) - rotationAdjustment


    return Path { p in


    p.addArc (


    center: CGPoint(x: rect.midX, y: rect.midY),


    radius: rect.width / 2,


    startAngle: adjustedStartAngle, endAngle: adjustedEndAngle,


    clockwise: false


    )


    p.addLine(to: CGPoint(x: rect.midX, y: rect.midY))


    p.closeSubpath()


    }


    }


    }
    ArcViewͷ࣮૷
    ԁހͷඳը


    addArc


    +


    addLine & close
    return Path { p in


    p.addArc (


    center: CGPoint(x: rect.midX, y: rect.midY),


    radius: rect.width / 2,


    startAngle: adjustedStartAngle,


    endAngle: adjustedEndAngle,


    clockwise: false


    )


    p.addLine(to: CGPoint(x: rect.midX, y: rect.midY))


    p.closeSubpath()


    } 15

    View Slide

  16. © ZOZO, Inc.
    PathͰԁάϥϑΛ࣮૷
    16
    ԁάϥϑ׬੒
    struct PieChartView: View {


    var body: some View {


    ZStack {


    ArcView(startAngle: 0, endAngle: 360)


    .fill(Color("green"))


    ArcView(startAngle: 0, endAngle: 250)


    .fill(Color("blue"))


    ArcView(startAngle: 0, endAngle: 140)


    .fill(Color("red"))


    }


    }


    }

    View Slide

  17. © ZOZO, Inc.
    PathͰԁάϥϑΛ࣮૷
    17
    ςΩετ΋ࠞͥͯ


    ͜Μͳ͜ͱ΋Ͱ͖Δ

    View Slide

  18. © ZOZO, Inc.
    ͞ΒʹPathͰϨʔμʔνϟʔτ΋࣮૷Մೳ
    ● addLine(to:), move(to:) ͷΈ


    ● ଟ֯ܗͷ֤௖఺Λܭࢉ͠ɺ

    ௖఺Λͭͳ͛ͯଟ֯ܗΛඳը
    18

    View Slide

  19. © ZOZO, Inc.
    ͞ΒʹPathͰϨʔμʔνϟʔτ΋࣮૷Մೳ
    19
    ϕʔεͷViewͷ࡞੒ํ๏Λ


    ͬ͘͟Γ͝঺հ
    PolygonShape

    View Slide

  20. © ZOZO, Inc.
    struct PolygonShape: Shape {


    let sides: Int // ลͷ਺


    func path(in rect: CGRect) -> Path {


    let centerX = rect.width / 2


    let centerY = rect.height / 2


    let radius = min(rect.width, rect.height) / 2


    let angle = .pi * 2 / Double(sides)


    let offsetAngle = -(Double.pi / 2) // 0౓Λ্෦ʹ͢ΔͨΊʹ֯౓ௐ੔


    return Path { path in


    for index in 0..

    let currentAngle = angle * Double(index) + offsetAngle


    let x = centerX + cos(currentAngle) * radius


    let y = centerY + sin(currentAngle) * radius


    if index == 0 {


    path.move(to: CGPoint(x: x, y: y))


    } else {


    path.addLine(to: CGPoint(x: x, y: y))


    }


    }


    path.closeSubpath()


    }


    }


    }
    Ϩʔμʔνϟʔτ - શମ૾
    20

    View Slide

  21. © ZOZO, Inc.
    struct PolygonShape: Shape {


    let sides: Int // ลͷ਺


    func path(in rect: CGRect) -> Path {


    let centerX = rect.width / 2


    let centerY = rect.height / 2


    let radius = min(rect.width, rect.height) / 2


    let angle = .pi * 2 / Double(sides)


    let offsetAngle = -(Double.pi / 2) // 0౓Λ্෦ʹ͢ΔͨΊʹ֯౓ௐ੔


    return Path { path in


    for index in 0..

    let currentAngle = angle * Double(index) + offsetAngle


    let x = centerX + cos(currentAngle) * radius


    let y = centerY + sin(currentAngle) * radius


    if index == 0 {


    path.move(to: CGPoint(x: x, y: y))


    } else {


    path.addLine(to: CGPoint(x: x, y: y))


    }


    }


    path.closeSubpath()


    }


    }


    }
    Ϩʔμʔνϟʔτ - શମ૾
    21

    View Slide

  22. © ZOZO, Inc.
    struct PolygonShape: Shape {


    let sides: Int // ลͷ਺


    func path(in rect: CGRect) -> Path {


    let centerX = rect.width / 2


    let centerY = rect.height / 2


    let radius = min(rect.width, rect.height) / 2


    let angle = .pi * 2 / Double(sides)


    let offsetAngle = -(Double.pi / 2) // 0౓Λ্෦ʹ͢ΔͨΊʹ֯౓ௐ੔


    return Path { path in


    for index in 0..

    let currentAngle = angle * Double(index) + offsetAngle


    let x = centerX + cos(currentAngle) * radius


    let y = centerY + sin(currentAngle) * radius


    if index == 0 {


    path.move(to: CGPoint(x: x, y: y))


    } else {


    path.addLine(to: CGPoint(x: x, y: y))


    }


    }


    path.closeSubpath()


    }


    }


    }
    Ϩʔμʔνϟʔτ - શମ૾
    22
    ลͷ਺Λఆٛ
    let sides: Int // ลͷ਺

    View Slide

  23. © ZOZO, Inc.
    struct PolygonShape: Shape {


    let sides: Int // ลͷ਺


    func path(in rect: CGRect) -> Path {


    let centerX = rect.width / 2


    let centerY = rect.height / 2


    let radius = min(rect.width, rect.height) / 2


    let angle = .pi * 2 / Double(sides)


    let offsetAngle = -(Double.pi / 2) // 0౓Λ্෦ʹ͢ΔͨΊʹ֯౓ௐ੔


    return Path { path in


    for index in 0..

    let currentAngle = angle * Double(index) + offsetAngle


    let x = centerX + cos(currentAngle) * radius


    let y = centerY + sin(currentAngle) * radius


    if index == 0 {


    path.move(to: CGPoint(x: x, y: y))


    } else {


    path.addLine(to: CGPoint(x: x, y: y))


    }


    }


    path.closeSubpath()


    }


    }


    }
    Ϩʔμʔνϟʔτ - શମ૾
    23

    View Slide

  24. © ZOZO, Inc.
    struct PolygonShape: Shape {


    let sides: Int // ลͷ਺


    func path(in rect: CGRect) -> Path {


    let centerX = rect.width / 2


    let centerY = rect.height / 2


    let radius = min(rect.width, rect.height) / 2


    let angle = .pi * 2 / Double(sides)


    let offsetAngle = -(Double.pi / 2) // 0౓Λ্෦ʹ͢ΔͨΊʹ֯౓ௐ੔


    return Path { path in


    for index in 0..

    let currentAngle = angle * Double(index) + offsetAngle


    let x = centerX + cos(currentAngle) * radius


    let y = centerY + sin(currentAngle) * radius


    if index == 0 {


    path.move(to: CGPoint(x: x, y: y))


    } else {


    path.addLine(to: CGPoint(x: x, y: y))


    }


    }


    path.closeSubpath()


    }


    }


    }
    Ϩʔμʔνϟʔτ - શମ૾
    24
    Ҿ਺ CGRect ͔Βத৺఺ɺ൒ܘɺ֯౓ͷࢉग़
    let centerX = rect.width / 2


    let centerY = rect.height / 2


    let radius = min(rect.width, rect.height) / 2


    let angle = .pi * 2 / Double(sides)


    let offsetAngle = -(Double.pi / 2)

    View Slide

  25. © ZOZO, Inc.
    struct PolygonShape: Shape {


    let sides: Int // ลͷ਺


    func path(in rect: CGRect) -> Path {


    let centerX = rect.width / 2


    let centerY = rect.height / 2


    let radius = min(rect.width, rect.height) / 2


    let angle = .pi * 2 / Double(sides)


    let offsetAngle = -(Double.pi / 2) // 0౓Λ্෦ʹ͢ΔͨΊʹ֯౓ௐ੔


    return Path { path in


    for index in 0..

    let currentAngle = angle * Double(index) + offsetAngle


    let x = centerX + cos(currentAngle) * radius


    let y = centerY + sin(currentAngle) * radius


    if index == 0 {


    path.move(to: CGPoint(x: x, y: y))


    } else {


    path.addLine(to: CGPoint(x: x, y: y))


    }


    }


    path.closeSubpath()


    }


    }


    }
    Ϩʔμʔνϟʔτ - શମ૾
    25

    View Slide

  26. © ZOZO, Inc.
    struct PolygonShape: Shape {


    let sides: Int // ลͷ਺


    func path(in rect: CGRect) -> Path {


    let centerX = rect.width / 2


    let centerY = rect.height / 2


    let radius = min(rect.width, rect.height) / 2


    let angle = .pi * 2 / Double(sides)


    let offsetAngle = -(Double.pi / 2) // 0౓Λ্෦ʹ͢ΔͨΊʹ֯౓ௐ੔


    return Path { path in


    for index in 0..

    let currentAngle = angle * Double(index) + offsetAngle


    let x = centerX + cos(currentAngle) * radius


    let y = centerY + sin(currentAngle) * radius


    if index == 0 {


    path.move(to: CGPoint(x: x, y: y))


    } else {


    path.addLine(to: CGPoint(x: x, y: y))


    }


    }


    path.closeSubpath()


    }


    }


    }
    Ϩʔμʔνϟʔτ - શମ૾
    26
    ֤௖఺ͷ֯౓Λܭࢉ


    ࠷ॳͷ௖఺ͳΒ͹ύεͷ։࢝఺Λઃఆ


    ͦΕҎ֎ͳΒ௚ઢΛҾ͘
    return Path { path in


    for index in 0..

    let currentAngle = angle * Double(index) + offsetAngle


    let x = centerX + cos(currentAngle) * radius


    let y = centerY + sin(currentAngle) * radius


    if index == 0 {


    path.move(to: CGPoint(x: x, y: y))


    } else {


    path.addLine(to: CGPoint(x: x, y: y))


    }


    }


    path.closeSubpath()


    }

    View Slide

  27. © ZOZO, Inc.
    ͞ΒʹPathͰϨʔμʔνϟʔτ΋࣮૷Մೳ
    27
    PolygonShape
    ϕʔεͷView׬੒

    View Slide

  28. © ZOZO, Inc.
    ͡Ό͋PathͰάϥϑͷ࡞੒ʹݶք͸͋Δͷʁʁ
    2DͷඳըքͰ͸ݶք͸ແ͍ʂʁʢFuyaௐ΂ʣ
    28

    View Slide

  29. © ZOZO, Inc.
    ·ͱΊ
    Swift ChartsͰαϙʔτ͞Ε͍ͯͳ͍άϥϑ͸

    PathΛ࢖࣮ͬͯ૷͢Δ͜ͱ͕Մೳ
    29
    ৄ͍࣮͠૷ํ๏͸ͪ͜Β͔Β


    https://github.com/Fuyan777/charts-sample

    View Slide

  30. View Slide