首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 結合 Open Flash Chart 繪製互動式圖表完整指南:柱狀圖、趨勢圖、資料串流與現代化替代方案

     編輯:PHP 相關知識

Open Flash Chart (OFC) 曾是 PHP 產生互動式圖表的熱門選擇,雖然 Flash 技術已淘汰,但其「PHP 產生 JSON、前端渲染」的架構思想仍具參考價值。本文回顧 OFC 整合方式,並提供現代化 JavaScript 圖表庫替代方案對照表。

Open Flash Chart 架構回顧


傳統 OFC 架構:
┌─────────────┐     JSON      ┌──────────────┐     SWF      ┌─────────────┐
│  PHP 後端   │ ───────────▶  │  瀏覽器      │ ───────────▶ │  Flash Player │
│  產生資料   │  data-file    │  載入 SWF   │  渲染圖表    │  (已淘汰)   │
└─────────────┘               └──────────────┘              └─────────────┘
      │                            │
      │ 替代: get-data 回呼函數      │ 替代: JS 圖表庫
      ▼                            ▼
┌─────────────┐               ┌──────────────┐
│  PHP API    │               │  Chart.js    │
│  回傳 JSON  │               │  ECharts     │
└─────────────┘               └──────────────┘

PHP 端資料產生範例 (架構參考)

<?php
// api/chart_data.php
header('Content-Type: application/json; charset=utf-8');

/**
 * 通用圖表資料結構 (適用於 Chart.js, ECharts, ApexCharts 等)
 */
class ChartData {
    public array $labels = [];
    public array $datasets = [];
    public ?string $title = null;
    
    public function addDataset(string $label, array $data, array $options = []): self {
        $this->datasets[] = array_merge([
            'label' => $label,
            'data' => $data,
            'borderWidth' => 2,
            'fill' => false,
        ], $options);
        return $this;
    }
    
    public function setLabels(array $labels): self {
        $this->labels = $labels;
        return $this;
    }
    
    public function toJson(): string {
        return json_encode([
            'labels' => $this->labels,
            'datasets' => $this->datasets,
            'title' => $this->title,
        ], JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK);
    }
}

// 範例:銷售趨勢圖
$chart = new ChartData();
$chart->title = '2024 年月銷售趨勢';
$chart->setLabels(['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月']);
$chart->addDataset('線上銷售', [120, 132, 101, 134, 90, 230, 210, 225, 180, 195, 210, 240], [
    'borderColor' => 'rgb(54, 162, 235)',
    'backgroundColor' => 'rgba(54, 162, 235, 0.1)',
    'tension' => 0.3,
]);
$chart->addDataset('實體門市', [80, 95, 88, 110, 105, 150, 140, 160, 130, 145, 155, 170], [
    'borderColor' => 'rgb(255, 99, 132)',
    'backgroundColor' => 'rgba(255, 99, 132, 0.1)',
    'tension' => 0.3,
]);

echo $chart->toJson();

前端整合:Chart.js 現代化實作

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>銷售趨勢圖 - Chart.js</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
    <style>
        .chart-container { max-width: 800px; margin: 2rem auto; }
    </style>
</head>
<body>
    <div class="chart-container">
        <canvas id="salesChart"></canvas>
    </div>

    <script>
    async function loadChart() {
        try {
            const response = await fetch('/api/chart_data.php');
            const data = await response.json();
            
            const ctx = document.getElementById('salesChart').getContext('2d');
            new Chart(ctx, {
                type: 'line',
                data: {
                    labels: data.labels,
                    datasets: data.datasets
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        title: {
                            display: true,
                            text: data.title,
                            font: { size: 18 }
                        },
                        legend: { position: 'top' },
                        tooltip: { mode: 'index', intersect: false }
                    },
                    scales: {
                        y: { beginAtZero: true, title: { display: true, text: '銷售額 (萬元)' } },
                        x: { title: { display: true, text: '月份' } }
                    },
                    interaction: { mode: 'nearest', axis: 'x', intersect: false }
                }
            });
        } catch (e) {
            console.error('圖表載入失敗:', e);
        }
    }
    
    loadChart();
    </script>
</body>
</html>

ECharts 企業級功能範例

<div id="echart-bar" style="width: 100%; height: 400px;"></div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<script>
async function loadEChart() {
    const res = await fetch('/api/chart_data.php');
    const data = await res.json();
    
    const chart = echarts.init(document.getElementById('echart-bar'));
    chart.setOption({
        title: { text: data.title, left: 'center' },
        tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
        legend: { top: '5%', data: data.datasets.map(d => d.label) },
        toolbox: { 
            show: true, 
            feature: { 
                saveAsImage: {}, 
                dataView: { readOnly: false }, 
                restore: {}, 
                dataZoom: { yAxisIndex: 'none' } 
            } 
        },
        dataZoom: [{ type: 'inside' }, { type: 'slider', bottom: 0 }],
        xAxis: { type: 'category', data: data.labels },
        yAxis: { type: 'value', name: '銷售額' },
        series: data.datasets.map(ds => ({
            name: ds.label,
            type: 'bar',
            data: ds.data,
            itemStyle: { color: ds.backgroundColor || ds.borderColor },
            emphasis: { focus: 'series' }
        }))
    });
    
    // 響應式調整
    window.addEventListener('resize', () => chart.resize());
}
loadEChart();
</script>

資料串流與即時更新

<?php
// api/realtime_data.php - Server-Sent Events (SSE)
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');

$lastId = isset($_SERVER['HTTP_LAST_EVENT_ID']) ? (int)$_SERVER['HTTP_LAST_EVENT_ID'] : 0;

// 模擬即時資料來源
function getLatestMetrics(int $sinceId): array {
    // 實際從 Redis/DB 讀取
    return [
        'id' => $sinceId + 1,
        'timestamp' => date('H:i:s'),
        'cpu' => rand(10, 90),
        'memory' => rand(30, 85),
        'requests' => rand(100, 5000),
    ];
}

while (true) {
    $data = getLatestMetrics($lastId);
    echo "id: {$data['id']}\n";
    echo "data: " . json_encode($data) . "\n\n";
    @ob_flush();
    @flush();
    $lastId = $data['id'];
    sleep(2); // 2 秒推送一次
    
    // 檢查客戶端連線
    if (connection_aborted()) break;
}
<!-- 前端 SSE 接收 -->
<script>
const evtSource = new EventSource('/api/realtime_data.php');
const chart = /* Chart.js 實例 */;

evtSource.onmessage = function(e) {
    const data = JSON.parse(e.data);
    // 滾動更新:移除最舊、加入最新
    if (chart.data.labels.length > 20) {
        chart.data.labels.shift();
        chart.data.datasets.forEach(ds => ds.data.shift());
    }
    chart.data.labels.push(data.timestamp);
    chart.data.datasets[0].data.push(data.cpu);
    chart.data.datasets[1].data.push(data.memory);
    chart.update('none'); // 無動畫更新
};

evtSource.onerror = () => console.log('SSE 連線中斷,嘗試重連...');
</script>

圖表庫選型對照表 (2024 現代化替代)

需求Chart.jsEChartsApexChartsHighchartsuPlot
授權MITApache 2.0MIT商業付費MIT
包大小 (gz)~60KB~150KB~80KB~100KB+~15KB
圖表類型8 種基礎20+ 種豐富15+ 種最完整時間序列專精
大數據量~5k 點~100k 點~50k 點~100k+百萬級 (Canvas/WebGL)
互動功能基本極豐富 (dataZoom, brush)良好最強基礎
TypeScript內建內建內建內建內建
框架整合Vue/React/Svelte 官方包Vue/React 官方包Vue/React 官方包官方包需自行封裝
SSR 支援佳佳 (Node 渲染)佳佳需處理
推薦場景通用儀表板、中小專案企業級 BI、複雜視覺化SaaS 產品、漸層動畫金融/科研/商業報表即時監控、高頻資料

PHP 端最佳實務封裝

<?php
/**
 * 圖表資料 API 基礎類別
 * 統一輸出格式、快取、權限、錯誤處理
 */
abstract class ChartApiController {
    protected int $cacheTtl = 300; // 5 分鐘快取
    
    public function handleRequest(): void {
        header('Content-Type: application/json; charset=utf-8');
        header('Cache-Control: public, max-age=' . $this->cacheTtl);
        header('Access-Control-Allow-Origin: *'); // 視需求調整
        
        try {
            // 權限檢查
            if (!$this->authorize()) {
                http_response_code(403);
                echo json_encode(['error' => 'Forbidden']);
                return;
            }
            
            // 參數驗證
            $params = $this->validateParams($_GET);
            
            // 快取鍵
            $cacheKey = 'chart:' . $this->getChartType() . ':' . md5(json_encode($params));
            $data = $this->getFromCache($cacheKey);
            
            if ($data === null) {
                $data = $this->buildChartData($params);
                $this->saveToCache($cacheKey, $data, $this->cacheTtl);
            }
            
            echo $this->formatResponse($data);
            
        } catch (Throwable $e) {
            http_response_code(500);
            error_log('Chart API Error: ' . $e->getMessage());
            echo json_encode(['error' => 'Internal server error']);
        }
    }
    
    abstract protected function getChartType(): string;
    abstract protected function buildChartData(array $params): array;
    abstract protected function validateParams(array $input): array;
    
    protected function authorize(): bool {
        return true; // 子類別覆寫
    }
    
    protected function getFromCache(string $key): ?string {
        // Redis/Memcached/APCu 實作
        return null;
    }
    
    protected function saveToCache(string $key, string $data, int $ttl): void {}
    
    protected function formatResponse(array $data): string {
        return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK);
    }
}

// 具體實作
class SalesTrendChartController extends ChartApiController {
    protected function getChartType(): string { return 'sales_trend'; }
    
    protected function validateParams(array $input): array {
        return [
            'year' => $input['year'] ?? date('Y'),
            'department' => $input['dept'] ?? 'all',
        ];
    }
    
    protected function buildChartData(array $params): array {
        // 從 DB 查詢...
        $labels = ['1月','2月','3月','4月','5月','6月'];
        $online = [120, 132, 101, 134, 90, 230];
        $offline = [80, 95, 88, 110, 105, 150];
        
        return [
            'labels' => $labels,
            'datasets' => [
                ['label' => '線上', 'data' => $online, 'borderColor' => '#36a2eb'],
                ['label' => '實體', 'data' => $offline, 'borderColor' => '#ff6384'],
            ],
            'title' => "{$params['year']} 年上半年銷售趨勢",
        ];
    }
}

// 入口
$controller = new SalesTrendChartController();
$controller->handleRequest();

總結

Open Flash Chart 的歷史使命已結束,但其後端產生資料規格、前端負責渲染的分離架構思想在現代圖表庫中完美傳承。選型建議:

  • 通用專案:Chart.js — 輕量、易上手、生態完善
  • 企業級 BI/儀表板:ECharts — 功能最豐富、大數據支援、工具箱完整
  • SaaS 產品/高互動:ApexCharts — 現代化 API、漸層動畫、Vue/React 整合佳
  • 高頻即時監控:uPlot — 極致效能、微型體積、時間序列專精

PHP 端應專注於資料查詢、權限控制、快取策略、統一輸出格式,避免在後端處理圖表樣式配置,保持前後端職責清晰分離。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved