サンプル・プログラムの実行例
サンプル・プログラム
| viewPolls.php | サンプル・プログラム本体。 |
| polls.xml | 内閣支持率・政党支持率のデータ・ファイル。 |
| viewPolls.php | サンプル・プログラム本体。 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 3.1.0 | 2026/08/12 | 新党4党の解析・グラフ表示に対応 |
| 3.0.0 | 2026/05/14 | データ読み込み方法を大幅改訂 |
| 2.2.0 | 2026/01/24 | PHP8.5対応:curl_closeを使わない |
| 3.5.0 | 2025/05/24 | 2024~2025年のHTML表記ゆれに対応 |
| 3.4.0 | 2024/05/12 | HTMLヘッダ見直し |
解説:準備
viewPolls.php
1: <?php
2: /** viewPolls.php
3: * NHK政治意識月例調査をグラフ表示
4: *
5: * @copyright (c)studio pahoo
6: * @author パパぱふぅ
7: * @動作環境 PHP 5/7/8
8: * @参考URL https://www.pahoo.org/e-soul/webtech/phpgd/phpgd-15-01.shtm
9: *
10: */
11: // 初期化処理 ================================================================
12: define('INTERNAL_ENCODING', 'UTF-8');
13: define('MYSELF', basename($_SERVER['SCRIPT_NAME']));
14: define('REFERENCE', 'https://www.pahoo.org/e-soul/webtech/phpgd/phpgd-15-01.shtm');
15: mb_internal_encoding(INTERNAL_ENCODING);
16: mb_regex_encoding(INTERNAL_ENCODING);
17:
18: // プログラム・タイトル
19: define('TITLE', 'NHK政治意識月例調査をグラフ表示');
20:
21: // 各種定数(START) ===========================================================
22:
23: // 描画期間(年)
24: define('PERIOD', 3);
25:
26: // 最古データ(年)
27: define('OLDYEAR', 1998);
28:
29: // グラフを描くDOMオブジェジェクト名
30: define('GRAPH_ID', 'jqPlot_polls');
31:
32: // 表示幅(ピクセル)
33: define('WIDTH', 600);
34:
35: // 表示高さ(ピクセル)
36: define('HEIGHT', 500);
37:
38: // 保存用データファイル名
39: define('POLLS_FILE', './polls.xml');
公式サイト「jqPlot Charts and Graphs for jQuery」から圧縮ファイルをダウンロードし、適当な場所に解凍しておく。その場所を定数 JQPLOT に定義する。
また、描画期間(当年から過去何年分遡るか)を定数 PERIOD に定義しておく。
なお、スクレイピングしたデータはXML形式ファイルとして定数 POLLS_FILE で示すファイルにセーブし、次に描画するときは、このファイルからロードできるようにした。
解説:スクレイピング
viewPolls.php
510: /**
511: * 内閣支持率を解析する
512: * @param string $contents 読み込んだコンテンツ
513: * @param array $items 解析結果を格納する配列
514: * @return bool TRUE/FALSE
515: */
516: function analyseCabinet($contents, &$items) {
517: $pat1 = "/<h[0-9]+[^>]*>内閣支持率<\/h[0-9]+>/u";
518: $pat2 = "/<tr[^>]*>/u";
519: $pat3 = "/<t[hd][^>]*>(<[^>]+>)?([0-9\-\.]+)/u";
520: $pat31 = "/<td[^>]*>(.+)/u";
521: $pat4 = "/<t[hd][^>]*>支持(<[^>]+>)?する/u";
522: $pat5 = "/<t[hd][^>]*>(<[^>]+>)?支持(<[^>]+>)?しない/u";
523: $pat6 = "/<t[hd][^>]*>内閣/u";
524: $pat61 = "/<\/table>/u";
525: $pat7 = "/<td colspan\=\"([0-9]+)\"[^>]*>([^<]+)/u";
526: $pat8 = "/<td[^>]*>([^<]+)/u";
527: $pat9 = "/<h[0-9]+[^>]*>.*政党支持率<\/h[0-9]+>/u";
528: $flag = 0;
529: foreach (explode("\n", $contents) as $line) {
530: $instr = trim($line);
531: if (($flag == 0) && (preg_match($pat1, $instr) > 0)) {
532: $flag = 1;
533: } else if (($flag == 1) && (preg_match($pat2, $instr) > 0)) {
534: $flag = 2;
535: $i = 0;
536: // 月
537: } else if ($flag == 2) {
538: if (preg_match($pat4, $instr) > 0) {
539: $flag = 3;
540: $i = 0;
541: } else if (preg_match($pat3, $instr, $arr) > 0) {
542: $items[$i]['month'] = $arr[2];
543: $items[$i]['yes'] = '-';
544: $items[$i]['no'] = '-';
545: $items[$i]['cabinet'] = '-';
546: $i++;
547: }
548: // 支持する
549: } else if ($flag == 3) {
550: if (preg_match($pat5, $instr) > 0) {
551: $flag = 4;
552: $i = 0;
553: } else if (preg_match($pat31, $instr, $arr) > 0) {
554: $items[$i]['yes'] = strip_tags($arr[1]);
555: $i++;
556: }
557: // 支持しない
558: } else if ($flag == 4) {
559: if (preg_match($pat31, $instr, $arr) > 0) {
560: $items[$i]['no'] = strip_tags($arr[1]);
561: $i++;
562: } else if (preg_match($pat6, $instr) > 0) {
563: $flag = 5;
564: $i = 0;
565: } else if (preg_match($pat61, $instr) > 0) {
566: $flag = 5;
567: $i = 0;
568: }
569: // 内閣
570: } else if ($flag == 5) {
571: if (preg_match($pat7, $instr, $arr) > 0) {
572: $n = $i + $arr[1];
573: while ($i < $n) {
574: $items[$i]['cabinet'] = $arr[2];
575: $i++;
576: }
577: } else if (preg_match($pat8, $instr, $arr) > 0) {
578: $items[$i]['cabinet'] = $arr[1];
579: $i++;
580: } else if (preg_match($pat9, $instr) > 0) {
581: $flag = 6;
582: }
583: } else if ($flag == 6) {
584: break;
585: }
586: }
587:
588: return TRUE;
589: }
viewPolls.php
697: /**
698: * 政党支持率を解析する
699: * @param string $contents 読み込んだコンテンツ
700: * @param array $items 解析結果を格納する配列
701: * @return bool TRUE/FALSE
702: */
703: function analyseParty($contents, &$items) {
704: $pat1 = "/<tr[^>]*>/u";
705: $pat2 = "/<t[hd][^>]*>(<[^>]+>)?([0-9\-\.]+)/u";
706: $pat3 = "/<\/tr>/u";
707: $pat4 = "/<t[hd][^>]*>([^<]+)/u";
708: $pat5 = "/<\/table>/u";
709: $flag = 0;
710: foreach (explode("\n", $contents) as $line) {
711: $instr = trim($line);
712: if (($flag == 0) && (preg_match($pat1, $instr) > 0)) {
713: $flag = 1;
714: $i = 0;
715: // 月
716: } else if ($flag == 1) {
717: if (preg_match($pat2, $instr, $arr) > 0) {
718: $items[$i]['month'] = $arr[2];
719: $i++;
720: } else if (preg_match($pat3, $instr) > 0) {
721: $flag = 2;
722: $i = 0;
723: }
724: // 政党支持率
725: } else if ($flag == 2) {
726: if (preg_match($pat2, $instr, $arr) > 0) {
727: $items[$i][$party] = $arr[2];
728: $i++;
729: } else if (preg_match($pat4, $instr, $arr) > 0) {
730: $party = $arr[1];
731: $i = 0;
732: } else if (preg_match($pat5, $instr) > 0) {
733: $flag = 3;
734: }
735: } else if ($flag == 3) {
736: break;
737: }
738: }
739: return TRUE;
740: }
スクレイピングしたデータは、配列に格納する。
viewPolls.php
591: /**
592: * 内閣支持率・政党支持率を解析する【2020年1月以降】
593: * @param string $contents 読み込んだコンテンツ
594: * @param array $items 内閣支持率の解析結果を格納する配列
595: * @param array $items 政党支持率の解析結果を格納する配列
596: * @return bool TRUE/FALSE
597: */
598: function analyse2020($contents, &$items1, &$items2) {
599: // プロットする政党名
600: static $table = array(
601: '', '自民党', '立憲民主党', '日本維新の会', '公明党', '共産党', '国民民主党', 'れいわ新選組', 'いのちの党', '社民党', 'みんなでつくる党', '参政党', '中道改革連合', 'チームみらい', '日本保守党', '特に支持している政党はない', '支持政党なし');
602:
603: $pat01 = "/<i\s+class\=\"far\s+fa\-calendar\-alt\"><\/i>[0-9]+年([0-9]+)月(<span>|\s*)/ui";
604: $pat11 = "/<h3>内閣支持([0-9]+)[%\%]、不支持([0-9]+)[%\%]/ui";
605: // $pat12 = "/(\p{Han}+内閣)を「支持する/ui";
606: $pat12 = "/([一-龠]+内閣)を「支持する/ui";
607: $pat21 = "/<h2><span>政党支持率<\/span>/ui";
608: $pat31 = "/<td>([^<]+)<\/td>/ui";
609: $pat32 = "/<td\s+class\=\"right\">([0-9\.]+)<\/td>/ui";
610: // 2023年8月以降
611: $pat22 = "/各党の支持率は/ui";
612: $pat33 = '/「([^」]+)」が([0-9\.]+)[%\%]/ui';
613:
614: $flag = FALSE;
615: foreach (explode("\n", $contents) as $line) {
616: $instr1 = trim($line);
617: $instr2 = strip_tags($instr1);
618: // 月
619: if (preg_match($pat01, $instr1, $arr) > 0) {
620: $month = (int)$arr[1];
621: // 内閣支持率・不支持率
622: } else if (preg_match($pat11, $instr1, $arr) > 0) {
623: $items1[$month - 1]['month'] = (int)$month;
624: $items1[$month - 1]['yes'] = (int)$arr[1];
625: $items1[$month - 1]['no'] = (int)$arr[2];
626: // 内閣名
627: } else if (preg_match($pat12, $instr2, $arr) > 0) {
628: $items1[$month - 1]['cabinet'] = (string)$arr[1];
629: // 政党名と支持率 2023年8月以降
630: } else if (preg_match($pat22, $instr1) > 0) {
631: $items2[$month - 1]['month'] = (int)$month;
632: if (preg_match_all($pat33, $instr1, $arr, PREG_PATTERN_ORDER) > 0) {
633: foreach ($arr[1] as $key=>$party) {
634: if ($party == '特に支持している政党はない') {
635: $party = '支持政党なし';
636: }
637: if (array_search($party, $table) != FALSE) {
638: $items2[$month - 1][$party] = (string)$arr[2][$key];
639: }
640: }
641: }
642: break;
643: // 政党支持率の解析開始
644: } else if (preg_match($pat21, $instr1, $arr) > 0) {
645: $flag = TRUE;
646: $items2[$month - 1]['month'] = (int)$month;
647: // 政党名
648: } else if ($flag && (preg_match($pat31, $instr1, $arr) > 0)) {
649: $party = (string)$arr[1];
650: if ($party == '特に支持している政党はない') {
651: $party = '支持政党なし';
652: }
653: // 支持率
654: } else if ($flag && (preg_match($pat32, $instr1, $arr) > 0)) {
655: $items2[$month - 1][$party] = (string)$arr[1];
656: }
657: }
658: return TRUE;
659: }
解説:XMLへの格納
viewPolls.php
661: /**
662: * 内閣支持率をXMLオブジェクトに追加/更新する
663: * @param array $items 解析結果を格納した配列
664: * @param int $year 西暦年
665: * @param object $xml XMLオブジェクト
666: * @return bool TRUE/FALSE
667: */
668: function addCabinet($items, $year, &$xml) {
669: // 西暦年の頭出し
670: $flag = FALSE;
671: foreach ($xml->research as $elem1) {
672: if ($elem1->year == $year) {
673: break;
674: }
675: }
676: // 月次処理
677: foreach ($items as $item) {
678: foreach ($elem1->article as $elem2) {
679: if ($elem2->month == $item['month']) {
680: $flag = TRUE;
681: break;
682: }
683: }
684: if (! $flag) {
685: $elem2 = $elem1->addChild('article');
686: $elem2->addChild('month', $item['month']);
687: }
688: if (! isset($elem2->cabinet)) {
689: $elem2->addChild('cabinet');
690: $elem2->cabinet->addChild('name', $item['cabinet']);
691: $elem2->cabinet->addChild('yes', $item['yes']);
692: $elem2->cabinet->addChild('no', $item['no']);
693: }
694: }
695: }
viewPolls.php
742: /**
743: * 政党支持率をXMLオブジェクトに追加/更新する
744: * @param array $items 解析結果を格納した配列
745: * @param int $year 西暦年
746: * @param object $xml XMLオブジェクト
747: * @return bool TRUE/FALSE
748: */
749: function addParty($items, $year, &$xml) {
750: // 西暦年の頭出し
751: $flag = FALSE;
752: foreach ($xml->research as $elem1) {
753: if ($elem1->year == $year) {
754: break;
755: }
756: }
757:
758: // 月次処理
759: foreach ($items as $item) {
760: foreach ($elem1->article as $elem2) {
761: if (isset($item['month']) && ($elem2->month == $item['month'])) {
762: $flag = TRUE;
763: break;
764: }
765: }
766: if (! $flag && isset($item['month'])) {
767: $elem2 = $elem1->addChild('article');
768: $elem2->addChild('month', $item['month']);
769: }
770:
771: foreach ($item as $key=>$val) {
772: if ($key == 'month') continue;
773: $flag = FALSE;
774: foreach ($elem2->party as $elem3) {
775: if (isset($elem3->name) && ($elem3->name == $key)) {
776: $flag = TRUE;
777: break;
778: }
779: }
780: if (! $flag) {
781: $elem4 = $elem2->addChild('party');
782: $elem4->addChild('name', $key);
783: $elem4->addChild('yes', $val);
784: }
785: }
786: }
787: }
解説:XMLファイルへのセーブとロード
viewPolls.php
473: /**
474: * データ・ファイルを書き込む
475: * @param string $fname 出力ファイル名
476: * @param object XMLオブジェクト
477: * @return bool TRUE/FALSE
478: */
479: function writeDataFile($fname, $xml) {
480: $str = $xml->asXML();
481: $str = cleanUpXML($str);
482: $outfp = fopen($fname, 'w');
483: fwrite($outfp, $str);
484:
485: return fclose($outfp);
486: }
viewPolls.php
446: /**
447: * データ・ファイルを読み込む
448: * @param string $fname 入力ファイル名
449: * @return object XMLオブジェクト/FALSE:読み込み失敗
450: */
451: function readDataFile($fname) {
452: $xmlstr =<<< EOT
453: <?xml version="1.0" encoding="utf-8" ?>
454: <polls>
455: </polls>
456:
457: EOT;
458: if (! isphp5over()) return FALSE; // PHP5以上でないと動作しない
459:
460: // ファイル無し
461: if (! file_exists($fname)) {
462: $xml = new SimpleXMLElement($xmlstr);
463:
464: // ファイル有り
465: } else {
466: $xml = simplexml_load_file($fname);
467: if ($xml == FALSE) return FALSE;
468: }
469:
470: return $xml;
471: }
解説:jqPlotの導入
公式サイト http://www.jqplot.com/ から最新のライブラリをダウンロードしたら、本プログラム冒頭の定数 JQPLOT で示すディレクトリへ解凍する。jqPlot は jQUery UI を使うので、jQueryも定数 JQPLOT で示すディレクトリへ格納してほしい。jQuery 1.7.1 以降のmin版があればよい。また、jqPlot は canvas文を使うのでHTML5対応ブラウザがあった方がいいが、IE8以前にも対応するよう代替スクリプトを用意している。
viewPolls.php
1: <?php
2: /** viewPolls.php
3: * NHK政治意識月例調査をグラフ表示
4: *
5: * @copyright (c)studio pahoo
6: * @author パパぱふぅ
7: * @動作環境 PHP 5/7/8
8: * @参考URL https://www.pahoo.org/e-soul/webtech/phpgd/phpgd-15-01.shtm
9: *
10: */
11: // 初期化処理 ================================================================
12: define('INTERNAL_ENCODING', 'UTF-8');
13: define('MYSELF', basename($_SERVER['SCRIPT_NAME']));
14: define('REFERENCE', 'https://www.pahoo.org/e-soul/webtech/phpgd/phpgd-15-01.shtm');
15: mb_internal_encoding(INTERNAL_ENCODING);
16: mb_regex_encoding(INTERNAL_ENCODING);
17:
18: // プログラム・タイトル
19: define('TITLE', 'NHK政治意識月例調査をグラフ表示');
20:
21: // 各種定数(START) ===========================================================
22:
23: // 描画期間(年)
24: define('PERIOD', 3);
25:
26: // 最古データ(年)
27: define('OLDYEAR', 1998);
28:
29: // グラフを描くDOMオブジェジェクト名
30: define('GRAPH_ID', 'jqPlot_polls');
31:
32: // 表示幅(ピクセル)
33: define('WIDTH', 600);
34:
35: // 表示高さ(ピクセル)
36: define('HEIGHT', 500);
37:
38: // 保存用データファイル名
39: define('POLLS_FILE', './polls.xml');
40:
41: // 偽装ユーザー
42: define('USER_AGENT', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
43:
44: // NHK政治意識月例調査URL【変更不可】
45: define('URL_NHK', 'http://www.nhk.or.jp/bunken/yoron/political/');
46:
47: // NHK選挙WEB URL【変更不可】
48: define('URL2_NHK', 'https://www.nhk.or.jp/senkyo/shijiritsu/');
49:
50: // 各種定数(END) ===============================================================
51:
52: // リファラチェック+リリースフラグの設定
53: if (isset($_SERVER['HTTP_HOST']) && ($_SERVER['HTTP_HOST'] == 'localhost')) {
54: define('FLAG_RELEASE', FALSE);
55: define('REFER_ON', '');
56: ini_set('display_errors', 1);
57: ini_set('error_reporting', E_ALL);
58: } else {
59: // リリース・フラグ(公開時にはTRUEにすること)
60: define('FLAG_RELEASE', TRUE);
61: // リファラ・チェック(直リン防止用;空文字ならチェックしない)
62: define('REFER_ON', 'www.pahoo.org');
63: }
64:
65: /**
66: * 処理選択肢
67: * @global array $SelectFuncs
68: */
69: $SelectFuncs = array(
70: // 関数名 タイトル ラジオボタンcheked
71: 'plotCabinet' => array('title'=>'内閣支持率', 'checked'=>'checked'),
72: 'plotParty' => array('title'=>'政党支持率', 'checked'=>'')
73: );
74:
75: /**
76: * 共通HTMLヘッダ
77: * @global string $HtmlHeader
78: */
79: $encode = INTERNAL_ENCODING;
80: $title = TITLE;
81: $width = WIDTH;
82: $HtmlHeader =<<< EOT
83: <!DOCTYPE html>
84: <html lang="ja">
85: <head>
86: <meta charset="{$encode}">
87: <title>{$title}</title>
88: <meta name="author" content="studio pahoo">
89: <meta name="copyright" content="studio pahoo">
90: <meta name="ROBOTS" content="NOINDEX,NOFOLLOW">
91: <meta http-equiv="pragma" content="no-cache">
92: <meta http-equiv="cache-control" content="no-cache">
93: <meta http-equiv="X-UA-Compatible" content="IE=edge">
94: <meta name="viewport" content="width={$width},user-scalable=yes">
95: <!-- jqPlot 本体(START) -->
96: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/jquery.jqplot.min.css">
ここでは、jqPlotプラグインの「jqplot.dateAxisRenderer.js」(時間軸を扱う)、「jqplot.highlighter.js」(ツールチップなどハイライト表示を行う)、「jqplot.pointLabels.js」(データポイントラベルを表示する)の3つを使っている。
このように、jqPlotは使いたい機能に応じてJavaScriptのプラグインを読み込む形をとる。
jqPlotの使い方は、「jqPlot - jQuery プラグイン」(アルファシス)が詳しい。
解説:jqPlotスクリプト
viewPolls.php
926: /**
927: * jqPlot用のスクリプト:内閣支持率
928: * @param object $xml XMLオブジェクト
929: * @param int $start 開始年
930: * @param int $finish 終了年
931: * @return string スクリプト
932: */
933: function plotCabinet($xml, $start, $finish) {
934: $graphId = GRAPH_ID;
935:
936: // 系列の生成
937: $series1 = ''; // 内閣支持率
938: $series2 = ''; // 内閣不支持率
939: $old = '';
940: foreach ($xml as $research) {
941: // 描画範囲内かどうか
942: $year = $research->year;
943: if (($year < $start) || ($year > $finish)) continue;
944: // 描画データ作成
945: foreach ($research as $article) {
946: $month = $article->month;
947: if ($month >= 1 && $month <= 12) {
948: $name = '';
949: if ($old != (string)$article->cabinet->name) {
950: $name = (string)$article->cabinet->name;
951: $old = (string)$article->cabinet->name;
952: }
953: $yes = $article->cabinet->yes;
954: $no = $article->cabinet->no;
955: if ($month > 0 && $yes > 0) {
956: $series1 .= sprintf("['%04d-%02d-01',%d,'<span style=\"background-color:#FFFFFF;\">%s</span>'],", $year, $month, $yes, $name);
957: $series2 .= sprintf("['%04d-%02d-01',%d,''],", $year, $month, $no);
958: }
959: }
960: }
961: }
962:
963: // X軸の最小値
964: $xmin = sprintf('%04d-01-01', $start);
965: // X軸の最大値
966: if (date('Y') == $finish) {
967: $xmax = date('Y-m-01', strtotime('next month'));
968: } else {
969: $xmax = sprintf('%04d-12-31', $finish);
970: }
971:
972: // グラフ描画
973: $js =<<< EOT
974: jQuery(function() {
975: jQuery.jqplot('{$graphId}',
976: [
977: [ {$series1} ],
978: [ {$series2} ]
979: ],
980: {
981: // 系列
982: series: [
983: { label: '支持率', color: '#88CC44' },
984: { label: '不支持率', color: '#CC4488' }
985: ],
986: legend: {
987: show: true,
988: placement: 'inside',
989: location: 'sw',
990: renderer: $.jqplot.EnhancedLegendRenderer,
991: rendererOptions: { numberRows: 1 }
992: },
993: seriesDefaults: {
994: showLine: true,
995: rendererOptions: { smooth: false },
996: markerOptions: { size: 0 },
997: pointLabels: {
998: show :true,
999: escapeHTML: false,
1000: location: 'ne'
1001: }
1002: },
1003: // 軸
1004: axes: {
1005: xaxis: {
1006: renderer:$.jqplot.DateAxisRenderer,
1007: min: '{$xmin}',
1008: max: '{$xmax}',
1009: tickOptions: { formatString: '%Y/%#m' },
1010: label: '年月',
1011: },
1012: yaxis: {
1013: label: '支持率(%)'
1014: }
1015: },
1016: // ハイライター
1017: highlighter: {
1018: show: true,
1019: showMarker: true,
1020: tooltipLocation: 'sw',
1021: fadeTooltip: false,
1022: bringSeriesToFront: true,
1023: tooltipAxes: 'xy',
1024: formatString: '%s<br>(不)支持率%s%'
1025: }
1026: }
1027: );
1028: });
1029:
1030: EOT;
1031:
1032: return $js;
1033: }
内閣支持率と不支持率の2本の折れ線グラフを描くのだが、各々、プロットする1つの点を "[x, y]" で表し、これをカンマで区切った文字列として生成する。ここでは "[日付, パーセント]" の形式で、支持率を変数 $seriese1 へ、不支持率を 変数 $seriese2 へ格納していく。
jqPlotスクリプトは jQuery(function() 以降の部分になる。
まず、グラフを描画する領域のDOMオブジェクト名を設定する。本プログラムでは、HTML側のdivタグで表示をしており、グラフの縦横サイズはdivタグのスタイルとして指定する。
上述のプロットデータ(文字列)はオブジェクト series に格納する。
オブジェクト legend は凡例の表示方法を指定する。表示場所(placement)はグラフの内側(inside)で、表示位置を南西(sw;下右)に、表示は1列(numberRows: 1)にする。表示を1列にするのに rendererOptionsを使うので、あわせて拡張機能 renderer: $.jqplot.EnhancedLegendRenderer を指定する。
オブジェクト seriesDefaults はグラフの表示方法を指定する。折れ線(showLine)を表示し(true)、折れ線のスムーズ処理(smooth)は行わず(false)、点(markerOptions)をプロットしない(size: 0)。
オブジェクト axes はX軸のラベルや目盛りの表示方法を指定する。X軸の最小値(min)は変数 $xmin を指定し、最大値(max)は $xmax を指定する。目盛りの書式(formatString)は年/月(%Y/%#m)にして、ラベル(label)は年月にする。ここで、目盛りの書式を使うために、拡張機能 renderer: $.jqplot.DateAxisRenderer を指定する。Y軸については、ラベルのみを表示する。
オブジェクト highlighter は、折れ線の上にマウスを乗せるデータを表示するハイライト表示する方法を指定する。ハイライターを表示し(show: true)、マーカーも表示し(showMarker: true)、表示位置(tooltipLocation)は南西(sw;下右)に、フェード効果(fadeTooltip)は無効に(false)、ハイライト表示した系列を他の系列よりも前面に移動し(bringSeriesToFront: true)、ツールチップにXとYの値を表示し(tooltipAxes: 'xy')、表示書式(formatString)は '%s
支持率%s%' に指定する。
viewPolls.php
1035: /**
1036: * jqPlot用のスクリプト:政党支持率
1037: * @param object $xml XMLオブジェクト
1038: * @param int $start 開始年
1039: * @param int $finish 終了年
1040: * @return string スクリプト
1041: */
1042: function plotParty($xml, $start, $finish) {
1043: $graphId = GRAPH_ID;
1044:
1045: // プロットする政党名
1046: static $table = array(
1047: '自民党', '公明党', '共産党', '民進党', '立憲民主党', '民主党', '日本維新の会', '国民民主党', 'れいわ新選組', 'いのちの党', '参政党', '中道改革連合', 'チームみらい', '日本保守党', '支持政党なし');
1048:
1049: // 系列の生成
1050: $str2 = '';
1051: foreach ($table as $key=>$val) {
1052: $str2 .= "\t\t\t{ label: '{$val}' },\n";
1053: $series[] = '';
1054: }
1055:
1056: foreach ($xml as $research) {
1057: $year = (int)$research->year;
1058: // 描画範囲内かどうか
1059: if (($year < $start) || ($year > $finish)) continue;
1060: // 描画データ作成
1061: foreach ($research as $article) {
1062: $month = (int)$article->month;
1063: foreach ($article as $party) {
1064: foreach ($table as $key=>$val) {
1065: if ($val == (string)$party->name) {
1066: if (preg_match('/[0-9\.]+/', $party->yes) > 0 && $year > 0 && $month >= 1 && $month <= 12) {
1067: $series[$key] .= sprintf("['%04d-%02d-01',%4.1f],", $year, $month, (float)$party->yes);
1068: }
1069: }
1070: }
1071: }
1072: }
1073: }
1074: $str1 = '';
1075: foreach ($series as $val) {
1076: $str1 .= "\t\t[ " . $val . "],\n";
1077: }
1078:
1079: // X軸の最小値
1080: $xmin = sprintf('%04d-01-01', $start);
1081: if (date('Y') == $finish) {
1082: $xmax = date('Y-m-01', strtotime('next month'));
1083: } else {
1084: $xmax = sprintf('%04d-12-31', $finish);
1085: }
1086:
1087: // グラフ描画
1088: $js =<<< EOT
1089: jQuery(function() {
1090: jQuery.jqplot('{$graphId}',
1091: [
1092: {$str1}
1093: ],
1094: {
1095: // 系列
1096: series: [
1097: {$str2}
1098: ],
1099: legend: {
1100: show: true,
1101: placement: 'inside',
1102: location: 'sw',
1103: renderer: $.jqplot.EnhancedLegendRenderer,
1104: rendererOptions: { numberRows: 1 }
1105: },
1106: seriesDefaults: {
1107: showLine: true,
1108: rendererOptions: { smooth: false },
1109: markerOptions: { size: 0 },
1110: },
1111: // 軸
1112: axes: {
1113: xaxis: {
1114: renderer: $.jqplot.DateAxisRenderer,
1115: min: '{$xmin}',
1116: max: '{$xmax}',
1117: tickOptions: { formatString: '%Y/%#m' },
1118: label: '年月',
1119: },
1120: yaxis: {
1121: label: '支持率(%)'
1122: }
1123: },
1124: // ハイライター
1125: highlighter: {
1126: show: true,
1127: showMarker: true,
1128: tooltipLocation: 'sw',
1129: fadeTooltip: false,
1130: bringSeriesToFront: true,
1131: tooltipAxes: 'xy',
1132: formatString: '%s<br>支持率%s%'
1133: }
1134: }
1135: );
1136: });
1137:
1138: EOT;
1139:
1140: return $js;
1141: }
補足:スクレイピング対策について
PHPの cURL関数や、Pythonの curl_cffi関数を使っても容易に読み出すことができないため、AIを使って定期的に "polls.xml" を更新し、当サイトにアップロードするようにしています。NHKが公表しているデータを正確に取得していますが、この方式に変更していることをご了承ください。

(2026年8月12日)新党4党(中道改革連合・チームみらい・いのちの党・日本保守党)の解析・グラフ表示に対応