buildWeatherPdfDocument function

Document buildWeatherPdfDocument({
  1. required HourlyWeatherData data,
  2. required Map<DateTime, double> dailyData,
  3. required Map<DateTime, (double, double)> dailyMinMax,
  4. Map<DateTime, double>? dailyMaxData,
  5. Map<DateTime, double>? dailyMinData,
  6. required double minValue,
  7. required double maxValue,
  8. DateTime? minDate,
  9. DateTime? maxDate,
  10. required String title,
  11. required String unit,
  12. String? dataType,
  13. double? latitude,
  14. double? longitude,
  15. String? address,
  16. Map<DateTime, int>? precipitationHours,
  17. 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;
}