buildWeatherPdfDocument function
- required HourlyWeatherData data,
- required Map<
DateTime, double> dailyData, - required Map<
DateTime, (double, double)> dailyMinMax, - Map<
DateTime, double> ? dailyMaxData, - Map<
DateTime, double> ? dailyMinData, - required double minValue,
- required double maxValue,
- DateTime? minDate,
- DateTime? maxDate,
- required String title,
- required String unit,
- String? dataType,
- double? latitude,
- double? longitude,
- String? address,
- Map<
DateTime, int> ? precipitationHours, - String? dataSource,
Build complete PDF document for weather data report.
Implementation
pw.Document buildWeatherPdfDocument({
required HourlyWeatherData data,
required Map<DateTime, double> dailyData,
required Map<DateTime, (double, double)> dailyMinMax,
Map<DateTime, double>? dailyMaxData,
Map<DateTime, double>? dailyMinData,
required double minValue,
required double maxValue,
DateTime? minDate,
DateTime? maxDate,
required String title,
required String unit,
String? dataType,
double? latitude,
double? longitude,
String? address,
Map<DateTime, int>? precipitationHours,
String? dataSource,
}) {
final pdf = pw.Document();
final dateFormat = DateFormat('yyyy-MM-dd');
pdf.addPage(
pw.MultiPage(
pageFormat: PdfPageFormat.a4,
margin: const pw.EdgeInsets.all(32),
build: (context) => [
// Title.
pw.Header(
level: 0,
child: pw.Text(
'Weather Data Report',
style: pw.TextStyle(fontSize: 24, fontWeight: pw.FontWeight.bold),
),
),
pw.SizedBox(height: 20),
// Location info.
if (address != null && address.isNotEmpty) ...[
pw.Text(
'Location: $address',
style: const pw.TextStyle(fontSize: 12),
),
pw.SizedBox(height: 4),
],
if (latitude != null && longitude != null)
pw.Text(
'Coordinates: ${latitude.toStringAsFixed(4)}, ${longitude.toStringAsFixed(4)}',
style: const pw.TextStyle(fontSize: 10, color: PdfColors.grey700),
),
pw.SizedBox(height: 10),
// Data source (forecast, past, historical)
if (dataSource != null) ...[
pw.Container(
padding: const pw.EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: pw.BoxDecoration(
color: PdfColors.blue50,
borderRadius: const pw.BorderRadius.all(pw.Radius.circular(4)),
border: pw.Border.all(color: PdfColors.blue200),
),
child: pw.Text(
dataSource,
style: pw.TextStyle(
fontSize: 12,
fontWeight: pw.FontWeight.bold,
color: PdfColors.blue800,
),
),
),
pw.SizedBox(height: 10),
],
// Date range.
pw.Text(
'Date Range: ${dateFormat.format(data.startDate)} - ${dateFormat.format(data.endDate)}',
style: const pw.TextStyle(fontSize: 12),
),
pw.SizedBox(height: 10),
// Data type.
pw.Text(
'Data Type: $title',
style: pw.TextStyle(fontSize: 14, fontWeight: pw.FontWeight.bold),
),
pw.SizedBox(height: 5),
// Data range with dates (for wind_speed, only show max)
if (dataType == 'wind_speed')
pw.RichText(
text: pw.TextSpan(
children: [
const pw.TextSpan(
text: 'Max Wind: ',
style: pw.TextStyle(fontSize: 12),
),
pw.TextSpan(
text: '${maxValue.toStringAsFixed(1)}$unit',
style: const pw.TextStyle(fontSize: 12),
),
if (maxDate != null)
pw.TextSpan(
text: ' (${DateFormat('MM/dd').format(maxDate)})',
style: const pw.TextStyle(
fontSize: 10,
color: PdfColors.grey600,
),
),
],
),
)
else
pw.RichText(
text: pw.TextSpan(
children: [
pw.TextSpan(
text: 'Min: ${minValue.toStringAsFixed(1)}$unit',
style: const pw.TextStyle(fontSize: 12),
),
if (minDate != null)
pw.TextSpan(
text: ' (${DateFormat('MM/dd').format(minDate)})',
style: const pw.TextStyle(
fontSize: 10,
color: PdfColors.grey600,
),
),
const pw.TextSpan(
text: ' ',
style: pw.TextStyle(fontSize: 12),
),
pw.TextSpan(
text: 'Max: ${maxValue.toStringAsFixed(1)}$unit',
style: const pw.TextStyle(fontSize: 12),
),
if (maxDate != null)
pw.TextSpan(
text: ' (${DateFormat('MM/dd').format(maxDate)})',
style: const pw.TextStyle(
fontSize: 10,
color: PdfColors.grey600,
),
),
],
),
),
pw.SizedBox(height: 20),
// Algorithm explanation.
pw.Container(
padding: const pw.EdgeInsets.all(12),
decoration: pw.BoxDecoration(
border: pw.Border.all(color: PdfColors.grey400),
borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)),
),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text(
'Data Processing Algorithms',
style: pw.TextStyle(
fontSize: 12,
fontWeight: pw.FontWeight.bold,
),
),
pw.SizedBox(height: 5),
pw.Text(
'Catmull-Rom spline interpolation: Smooth curve algorithm passing through data points',
style: const pw.TextStyle(fontSize: 10),
),
pw.SizedBox(height: 3),
pw.Text(
'Ramer-Douglas-Peucker algorithm: Smart sampling preserving key features',
style: const pw.TextStyle(fontSize: 10),
),
],
),
),
pw.SizedBox(height: 20),
// Chart visualization.
pw.Text(
'Data Visualization',
style: pw.TextStyle(fontSize: 14, fontWeight: pw.FontWeight.bold),
),
pw.SizedBox(height: 10),
// Use dual chart for temperature and wind_speed, single chart for others.
if ((dataType == 'temperature' || dataType == 'wind_speed') &&
dailyMaxData != null &&
dailyMinData != null)
buildPdfDualChart(
dailyMaxData,
dailyMinData,
minValue,
maxValue,
unit,
)
else
buildPdfChart(
dailyData,
minValue,
maxValue,
unit,
useActualRange: dataType == 'temperature' || dataType == 'humidity',
),
pw.SizedBox(height: 20),
// Daily data table.
pw.Text(
dataType == 'precipitation'
? 'Daily Total Data'
: 'Daily Average Data',
style: pw.TextStyle(fontSize: 14, fontWeight: pw.FontWeight.bold),
),
pw.SizedBox(height: 10),
buildPdfDataTable(
dailyData: dailyData,
dailyMinMax: dailyMinMax,
dataType: dataType ?? '',
unit: unit,
precipitationHours: precipitationHours,
),
pw.SizedBox(height: 20),
// Footer.
pw.Text(
'Generated: ${DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now())} UTC${formatTimeZoneOffset(DateTime.now().timeZoneOffset)}',
style: const pw.TextStyle(fontSize: 9, color: PdfColors.grey600),
),
],
),
);
return pdf;
}