Arc Layer in Flutter Maps (SfMaps)

19 Oct 202224 minutes to read

Arc layer is a sublayer that renders a group of MapArc on MapShapeLayer and MapTileLayer. This section helps to learn about how to add the arcs and customize them.

Adding arcs

The arcs is a collection of MapArc. Every single MapArc connects two location coordinates through a curved line. The start coordinate is set to MapArc.from property and the end coordinate is set to MapArc.to property.

NOTE

It is applicable for both the tile layer and shape layer.

In the shape layer

late MapZoomPanBehavior zoomPanBehavior;
late MapShapeSource dataSource;
late List<DataModel> data;

@override
void initState() {
  data = <DataModel>[
    DataModel(MapLatLng(28.6139, 77.2090), MapLatLng(39.9042, 116.4074)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(31.2304, 121.4737)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(23.1291, 113.2644)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(19.0760, 72.8777), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(22.3193, 114.1694), MapLatLng(13.0827, 80.2707)),
  ];

  dataSource = MapShapeSource.asset(
    'assets/world_map.json',
    shapeDataField: 'continent',
  );
  zoomPanBehavior = MapZoomPanBehavior(
    zoomLevel: 4,
    focalLatLng: MapLatLng(22.9734, 90.6569),
  );
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfMaps(
      layers: [
        MapShapeLayer(
          source: dataSource,
          sublayers: [
            MapArcLayer(
              arcs: List<MapArc>.generate(
                data.length,
                    (int index) {
                  return MapArc(
                    from: data[index].from,
                    to: data[index].to,
                  );
                },
              ).toSet(),
            ),
          ],
          zoomPanBehavior: zoomPanBehavior,
        ),
      ],
    ),
  );
}

class DataModel {
  DataModel(this.from, this.to);

  final MapLatLng from;
  final MapLatLng to;
}

In the tile layer

late MapZoomPanBehavior zoomPanBehavior;
late List<DataModel> data;

@override
void initState() {
  data = <DataModel>[
    DataModel(MapLatLng(28.6139, 77.2090), MapLatLng(39.9042, 116.4074)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(31.2304, 121.4737)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(23.1291, 113.2644)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(19.0760, 72.8777), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(22.3193, 114.1694), MapLatLng(13.0827, 80.2707)),
  ];

  zoomPanBehavior = MapZoomPanBehavior(
    zoomLevel: 3,
    focalLatLng: MapLatLng(22.9734, 90.6569),
  );
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfMaps(
      layers: [
        MapTileLayer(
          urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
          sublayers: [
            MapArcLayer(
              arcs: List<MapArc>.generate(
                data.length,
                    (int index) {
                  return MapArc(
                    from: data[index].from,
                    to: data[index].to,
                  );
                },
              ).toSet(),
            ),
          ],
          zoomPanBehavior: zoomPanBehavior,
        ),
      ],
    ),
  );
}

class DataModel {
  DataModel(this.from, this.to);

  final MapLatLng from;
  final MapLatLng to;
}

Default arc shape

Height factor

The heightFactor is the distance from the line connecting two points to the arc bend point. The default value of heightFactor property is 0.2 and the value ranges from -1 to 1.

By default, the arc will always render above the MapArc.from and MapArc.to points. To render the arc below the points, set the value between -1 to 0.

late MapZoomPanBehavior zoomPanBehavior;
late MapShapeSource dataSource;
late List<DataModel> data;

@override
void initState() {
  data = <DataModel>[
    DataModel(MapLatLng(28.6139, 77.2090), MapLatLng(39.9042, 116.4074)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(31.2304, 121.4737)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(19.0760, 72.8777), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(22.3193, 114.1694), MapLatLng(13.0827, 80.2707)),
  ];

  dataSource = MapShapeSource.asset(
    'assets/world_map.json',
    shapeDataField: 'continent',
  );
  zoomPanBehavior = MapZoomPanBehavior(
    zoomLevel: 4,
    focalLatLng: MapLatLng(22.9734, 90.6569),
  );
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfMaps(
      layers: [
        MapShapeLayer(
          source: dataSource,
          sublayers: [
            MapArcLayer(
              arcs: List<MapArc>.generate(
                data.length,
                    (int index) {
                  return MapArc(
                    from: data[index].from,
                    to: data[index].to,
                    heightFactor: -0.2,
                  );
                },
              ).toSet(),
            ),
          ],
          zoomPanBehavior: zoomPanBehavior,
        ),
      ],
    ),
  );
}

class DataModel {
  DataModel(this.from, this.to);

  final MapLatLng from;
  final MapLatLng to;
}

Arc height factor

Control point factor

The MapArc.controlPointFactor is the arc bending position. The default value of MapArc.controlPointFactor property is 0.5 and the value ranges from 0 to 1.

By default, the arc will bend at the center between the MapArc.from and MapArc.to points.

late MapZoomPanBehavior zoomPanBehavior;
late MapShapeSource dataSource;
late List<DataModel> data;

@override
void initState() {
  data = <DataModel>[
    DataModel(MapLatLng(28.6139, 77.2090), MapLatLng(39.9042, 116.4074)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(31.2304, 121.4737)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(19.0760, 72.8777), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(13.0827, 80.2707), MapLatLng(22.3193, 114.1694)),
  ];

  dataSource = MapShapeSource.asset(
    'assets/world_map.json',
    shapeDataField: 'continent',
  );
  zoomPanBehavior = MapZoomPanBehavior(
    zoomLevel: 4,
    focalLatLng: MapLatLng(22.9734, 90.6569),
  );
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfMaps(
      layers: [
        MapShapeLayer(
          source: dataSource,
          sublayers: [
            MapArcLayer(
              arcs: List<MapArc>.generate(
                data.length,
                    (int index) {
                  return MapArc(
                    from: data[index].from,
                    to: data[index].to,
                    controlPointFactor: 0.2,
                  );
                },
              ).toSet(),
            ),
          ],
          zoomPanBehavior: zoomPanBehavior,
        ),
      ],
    ),
  );
}

class DataModel {
  DataModel(this.from, this.to);

  final MapLatLng from;
  final MapLatLng to;
}

Arc control point factor

Color

You can apply the same color for all MapArc in the arcs collection using the MapArcLayer.color property. Alternatively, you can apply different colors to each MapArc in the arcs collection using the individual MapArc.color property.

late MapZoomPanBehavior zoomPanBehavior;
late MapShapeSource dataSource;
late List<DataModel> data;

@override
void initState() {
 data = <DataModel>[
   DataModel(MapLatLng(28.6139, 77.2090), MapLatLng(39.9042, 116.4074), Colors.redAccent),
   DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(31.2304, 121.4737), Colors.purpleAccent),
   DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(22.3193, 114.1694), Colors.deepPurple),
   DataModel(MapLatLng(19.0760, 72.8777), MapLatLng(22.3193, 114.1694), Colors.blueAccent),
   DataModel(MapLatLng(22.3193, 114.1694), MapLatLng(13.0827, 80.2707), Colors.teal),
 ];

  dataSource = MapShapeSource.asset(
    'assets/world_map.json',
    shapeDataField: 'continent',
  );
  zoomPanBehavior = MapZoomPanBehavior(
    zoomLevel: 4,
    focalLatLng: MapLatLng(22.9734, 90.6569),
  );
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfMaps(
      layers: [
        MapShapeLayer(
          source: dataSource,
          sublayers: [
            MapArcLayer(
              arcs: List<MapArc>.generate(
                data.length,
                    (int index) {
                  return MapArc(
                    from: data[index].from,
                    to: data[index].to,
                    color: data[index].color,
                  );
                },
              ).toSet(),
            ),
          ],
          zoomPanBehavior: zoomPanBehavior,
        ),
      ],
    ),
  );
}

class DataModel {
  DataModel(this.from, this.to, this.color);

  final MapLatLng from;
  final MapLatLng to;
  final Color color;
}

Arc color

Width

You can apply the same width for all MapArc in the arcs collection using the MapArcLayer.width property. Alternatively, you can apply different width to each MapArc in the arcs collection using the individual MapArc.width property. The default value of the MapArcLayer.width property is 2.

late MapZoomPanBehavior zoomPanBehavior;
late MapShapeSource dataSource;
late List<DataModel> data;

@override
void initState() {
  data = <DataModel>[
    DataModel(MapLatLng(28.6139, 77.2090), MapLatLng(39.9042, 116.4074), 2),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(31.2304, 121.4737), 3),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(23.1291, 113.2644), 2),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(22.3193, 114.1694), 4),
    DataModel(MapLatLng(19.0760, 72.8777), MapLatLng(22.3193, 114.1694), 5),
    DataModel(MapLatLng(22.3193, 114.1694), MapLatLng(13.0827, 80.2707), 6),
  ];

  dataSource = MapShapeSource.asset(
    'assets/world_map.json',
    shapeDataField: 'continent',
  );
  zoomPanBehavior = MapZoomPanBehavior(
    zoomLevel: 4,
    focalLatLng: MapLatLng(22.9734, 90.6569),
  );
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfMaps(
      layers: [
        MapShapeLayer(
          source: dataSource,
          sublayers: [
            MapArcLayer(
              arcs: List<MapArc>.generate(
                data.length,
                    (int index) {
                  return MapArc(
                    from: data[index].from,
                    to: data[index].to,
                    width: data[index].width,
                  );
                },
              ).toSet(),
            ),
          ],
          zoomPanBehavior: zoomPanBehavior,
        ),
      ],
    ),
  );
}

class DataModel {
  DataModel(this.from, this.to, this.width);

  final MapLatLng from;
  final MapLatLng to;
  final double width;
}

Arc width

Dash array

You can apply dash support for the arc using the MapArc.dashArray property.

A sequence of dash and gap will be rendered based on the values in this list. Once all values of the list is rendered, it will be repeated again till the end of the arc.

late MapZoomPanBehavior zoomPanBehavior;
late MapShapeSource dataSource;
late List<DataModel> data;

@override
void initState() {
  data = <DataModel>[
    DataModel(MapLatLng(28.6139, 77.2090), MapLatLng(39.9042, 116.4074)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(31.2304, 121.4737)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(19.0760, 72.8777), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(22.3193, 114.1694), MapLatLng(13.0827, 80.2707)),
  ];

  dataSource = MapShapeSource.asset(
    'assets/world_map.json',
    shapeDataField: 'continent',
  );
  zoomPanBehavior = MapZoomPanBehavior(
    zoomLevel: 4,
    focalLatLng: MapLatLng(22.9734, 90.6569),
  );
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfMaps(
      layers: [
        MapShapeLayer(
          source: dataSource,
          sublayers: [
            MapArcLayer(
              arcs: List<MapArc>.generate(
                data.length,
                    (int index) {
                  return MapArc(
                    from: data[index].from,
                    to: data[index].to,
                    dashArray: [8, 4, 2, 4],
                  );
                },
              ).toSet(),
              color: Colors.blue,
            ),
          ],
          zoomPanBehavior: zoomPanBehavior,
        ),
      ],
    ),
  );
}

class DataModel {
  DataModel(this.from, this.to);

  final MapLatLng from;
  final MapLatLng to;
}

Arc dash array

Animation

You can apply animation for the MapArc using the MapArcLayer.animation property and able to customize the animation flow, curve and duration.

By default, there will not be any animation.

class AnimationSample extends StatefulWidget {
  const AnimationSample({Key? key}) : super(key: key;
  @override
  State<AnimationSample> createState() => _AnimationSampleState();
}

class _AnimationSampleState extends State<AnimationSample> with TickerProviderStateMixin {
  late MapZoomPanBehavior zoomPanBehavior;
  late MapShapeSource dataSource;
  late List<DataModel> data;
  late AnimationController animationController;
  late Animation<double> animation;

  @override
  void initState() {
    data = <DataModel>[
      DataModel(MapLatLng(28.6139, 77.2090), MapLatLng(39.9042, 116.4074)),
      DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(31.2304, 121.4737)),
      DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(23.1291, 113.2644)),
      DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(22.3193, 114.1694)),
      DataModel(MapLatLng(19.0760, 72.8777), MapLatLng(22.3193, 114.1694)),
      DataModel(MapLatLng(22.3193, 114.1694), MapLatLng(13.0827, 80.2707)),
    ];

    dataSource = MapShapeSource.asset(
      'assets/world_map.json',
      shapeDataField: 'continent',
    );
    zoomPanBehavior = MapZoomPanBehavior(
      zoomLevel: 4,
      focalLatLng: MapLatLng(22.9734, 90.6569),
    );

    animationController = AnimationController(
      duration: Duration(seconds: 3),
      vsync: this,
    );
    animation = CurvedAnimation(
      parent: animationController,
      curve: Curves.easeInOut,
    );
    animationController.forward(from: 0);
    super.initState();
  }

  @override
  void dispose() {
    animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SfMaps(
        layers: [
          MapShapeLayer(
            source: dataSource,
            sublayers: [
              MapArcLayer(
                arcs: List<MapArc>.generate(
                  data.length,
                  (int index) {
                    return MapArc(
                      from: data[index].from,
                      to: data[index].to,
                    );
                  },
                ).toSet(),
                color: Colors.blue,
                animation: animation,
              ),
            ],
            zoomPanBehavior: zoomPanBehavior,
          ),
        ],
      ),
    );
  }
}

class DataModel {
  DataModel(this.from, this.to);

  final MapLatLng from;
  final MapLatLng to;
}

Arc animation

Tap

You can use the onTap callback to get a notification if the particular MapArc is tapped. You can also customize the tapped MapArc based on the index passed in the callback as shown in the below code snippet.

late MapZoomPanBehavior zoomPanBehavior;
late MapShapeSource dataSource;
late List<DataModel> data;
late int selectedIndex;

@override
void initState() {
  data = <DataModel>[
    DataModel(MapLatLng(28.6139, 77.2090), MapLatLng(39.9042, 116.4074)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(31.2304, 121.4737)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(23.1291, 113.2644)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(19.0760, 72.8777), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(22.3193, 114.1694), MapLatLng(13.0827, 80.2707)),
  ];

  dataSource = MapShapeSource.asset(
    'assets/world_map.json',
    shapeDataField: 'continent',
  );
  zoomPanBehavior = MapZoomPanBehavior(
    zoomLevel: 4,
    focalLatLng: MapLatLng(22.9734, 90.6569),
  );

  selectedIndex = -1;
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfMaps(
      layers: [
        MapShapeLayer(
          source: dataSource,
          sublayers: [
            MapArcLayer(
              arcs: List<MapArc>.generate(
                data.length,
                    (int index) {
                  return MapArc(
                    from: data[index].from,
                    to: data[index].to,
                    color: selectedIndex == index ? Colors.pink : Colors.blue,
                    onTap: () {
                       setState(() {
                         selectedIndex = index;
                       });
                    }
                  );
                },
              ).toSet(),
            ),
          ],
          zoomPanBehavior: zoomPanBehavior,
        ),
      ],
    ),
  );
}

class DataModel {
  DataModel(this.from, this.to);

  final MapLatLng from;
  final MapLatLng to;
}

Arc tap support

Tooltip

You can show additional information about an arc drawn using the tooltipBuilder property.

late MapZoomPanBehavior zoomPanBehavior;
late MapShapeSource dataSource;
late List<DataModel> data;
late Random random;

@override
void initState() {
  data = <DataModel>[
     DataModel(MapLatLng(28.6139, 77.2090), MapLatLng(39.9042, 116.4074)),
     DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(31.2304, 121.4737)),
     DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(22.3193, 114.1694)),
     DataModel(MapLatLng(19.0760, 72.8777), MapLatLng(22.3193, 114.1694)),
     DataModel(MapLatLng(22.3193, 114.1694), MapLatLng(13.0827, 80.2707)),
  ];

  dataSource = MapShapeSource.asset(
    'assets/world_map.json',
    shapeDataField: 'continent',
  );
  zoomPanBehavior = MapZoomPanBehavior(
    zoomLevel: 4,
    focalLatLng: MapLatLng(22.9734, 90.6569),
  );

  random = Random();
  super.initState();
}

@override
Widget build(BuildContext context) {
 final ThemeData themeData = Theme.of(context);
    final TextStyle textStyle = themeData.textTheme.caption!
        .copyWith(color: themeData.colorScheme.surface);
  return Scaffold(
    body: SfMaps(
      layers: [
        MapShapeLayer(
          source: dataSource,
          sublayers: [
            MapArcLayer(
              arcs: List<MapArc>.generate(
                data.length,
                    (int index) {
                  return MapArc(
                    from: data[index].from,
                    to: data[index].to,
                  );
                },
              ).toSet(),
              tooltipBuilder: (BuildContext context, int index) {
                 return Container(
                    padding: EdgeInsets.only(left: 5, top: 5),
                    height: 40,
                    width: 100,
                    child: Column(
                       children: [
                          Row(
                            children: [
                               Text('Flight   : ', style: textStyle),
                               Text('Air India', style: textStyle),
                            ],
                          ),
                          Row(
                             children: [
                                Text('Depart : ', style: textStyle),
                                Text(random.nextInt(12).toString() + 'AM', style: textStyle),
                             ],
                          ),
                       ],
                    ),
                );
              },
            ),
          ],
          zoomPanBehavior: zoomPanBehavior,
        ),
      ],
    ),
  );
}

class DataModel {
  DataModel(this.from, this.to);

  final MapLatLng from;
  final MapLatLng to;
}

Arc tooltip support

Tooltip customization

You can customize the appearance of the tooltip.

late MapZoomPanBehavior zoomPanBehavior;
late MapShapeSource dataSource;
late List<DataModel> data;
late Random random;

@override
void initState() {
  data = <DataModel>[
    DataModel(MapLatLng(28.6139, 77.2090), MapLatLng(39.9042, 116.4074)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(31.2304, 121.4737)),
    DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(19.0760, 72.8777), MapLatLng(22.3193, 114.1694)),
    DataModel(MapLatLng(22.3193, 114.1694), MapLatLng(13.0827, 80.2707)),
  ];

  dataSource = MapShapeSource.asset(
    'assets/world_map.json',
    shapeDataField: 'continent',
  );
  zoomPanBehavior = MapZoomPanBehavior(
    zoomLevel: 4,
    focalLatLng: MapLatLng(22.9734, 90.6569),
  );

  random = Random();
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfMaps(
      layers: [
        MapShapeLayer(
          source: dataSource,
          tooltipSettings: MapTooltipSettings(
             color: Colors.white,
             strokeColor: Colors.black,
             strokeWidth: 2,
          ),
          sublayers: [
            MapArcLayer(
              arcs: List<MapArc>.generate(
                data.length,
                    (int index) {
                  return MapArc(
                    from: data[index].from,
                    to: data[index].to,
                  );
                },
              ).toSet(),
              tooltipBuilder: (BuildContext context, int index) {
                 return Padding(
                    padding: EdgeInsets.all(5),
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Row(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Text('Flight   : '),
                            Text('Air India'),
                          ],
                        ),
                        Row(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Text('Depart : '),
                            Text(random.nextInt(12).toString() + 'AM'),
                          ],
                        ),
                      ],
                    ),
                );
              },
            ),
          ],
          zoomPanBehavior: zoomPanBehavior,
        ),
      ],
    ),
  );
}

class DataModel {
  DataModel(this.from, this.to);

  final MapLatLng from;
  final MapLatLng to;
}

Arc tooltip customization