text-to-chart.js 78 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435
  1. console.log('text-to-chart');
  2. var accessToken = localStorage.getItem("access_token");
  3. console.log(accessToken);
  4. if (accessToken === null) {
  5. alert('請先登入')
  6. window.location.href = "./login.html";
  7. }
  8. var usernameEmail = localStorage.getItem("username");
  9. var unit_table_value
  10. // let username = usernameEmail.split('@')[0];
  11. Chart.register(ChartDataLabels);
  12. var userNameSpan = document.getElementById("userName");
  13. var lineYfontSize = document.getElementById("lineYfontSize");
  14. userNameSpan.textContent = usernameEmail;
  15. var inputField = document.getElementById("keyword_data");
  16. var inputField_compare = document.getElementById("keyword_data_compare");
  17. var sendButton = document.getElementById("send_data");
  18. var canvasPng = document.getElementById("textToChart");
  19. var img_box = document.getElementById("img_box");
  20. var sidebar = document.getElementById('style_setting');
  21. var input_text_value;
  22. var chartType = 'line';
  23. var unit = '';
  24. var myChart = null;
  25. var chartColor = '#912B2B';
  26. var chartColorBar = '#'
  27. var displayXaxes = true;
  28. var ctx;
  29. var chartlabels;
  30. var chartdata;
  31. var XfontSizeValue = 18;
  32. var YfontSizeValue = 18;
  33. var TitlefontSizeValue = 32;
  34. var chart_bgimg_url = "url(./img/bg06.webp)";
  35. var vocab = [];
  36. var chartDiv = document.getElementById('chartdiv');
  37. let cancelToken;
  38. let source;
  39. $('#chartdiv').hide();
  40. document.addEventListener('DOMContentLoaded', function () {
  41. axios.get("https://cmm.ai:8080/vocab")
  42. .then(response => {
  43. // apiHideLoading();
  44. console.log(response);
  45. vocab = response.data;
  46. })
  47. .catch(error => {
  48. console.error('發生錯誤:', error);
  49. });
  50. });
  51. inputField.addEventListener('click', function () {
  52. // 確保輸入框處於焦點狀態
  53. inputField.focus();
  54. // 在控制台中顯示焦點狀態
  55. console.log('Input field focused');
  56. });
  57. document.addEventListener('DOMContentLoaded', function () {
  58. // const input = document.getElementById('stockInput');
  59. const suggestions = document.getElementById('suggestions');
  60. inputField.addEventListener('input', function () {
  61. const query = this.value.trim();
  62. suggestions.innerHTML = ''; // 清空之前的建議
  63. console.log('query', query);
  64. if (query) {
  65. // const filteredVocab = vocab.filter(item => item.toLowerCase().includes(query));
  66. const queryTerms = query.split(/\s+/); // 分割輸入的內容
  67. const lastQueryTerm = queryTerms[queryTerms.length - 1];
  68. const filteredVocab = vocab.filter(item => {
  69. // 對每個分割後的單詞進行匹配,只要其中有一個匹配成功就返回 true
  70. return item.toLowerCase().includes(lastQueryTerm.toLowerCase());
  71. });
  72. if (filteredVocab.length > 0) {
  73. suggestions.style.border = '1px solid #ccc';
  74. suggestions.style.display = 'block'; // 顯示建議容器
  75. } else {
  76. suggestions.style.border = 'none';
  77. suggestions.style.display = 'none'; // 隱藏建議容器
  78. }
  79. console.log(queryTerms[queryTerms.length - 1])
  80. filteredVocab.forEach(item => {
  81. const div = document.createElement('div');
  82. div.classList.add('autocomplete-suggestion');
  83. div.textContent = item;
  84. // div.addEventListener('click', function () {
  85. // inputField.value = item;
  86. // suggestions.innerHTML = ''; // 清空建議
  87. // suggestions.style.border = 'none';
  88. // suggestions.style.display = 'none'; // 隱藏建議容器
  89. // });
  90. console.log('queryTerms', queryTerms);
  91. // console.log('filteredVocab', filteredVocab);
  92. div.addEventListener('click', function () {
  93. queryTerms.forEach((term, index) => {
  94. const regex = new RegExp(term, 'gi');
  95. const match = regex.exec(inputField.value);
  96. // console.log(queryTerms[queryTerms.length - 1]);
  97. // console.log(filteredVocab, queryTerms[queryTerms.length - 1])
  98. if (index === queryTerms.length - 1) {
  99. if (match) {
  100. var start = match.index;
  101. var end = start + match[0].length;
  102. console.log(start, end)
  103. const textBefore = inputField.value.substring(0, start);
  104. const textAfter = inputField.value.substring(end, inputField.value.length);
  105. console.log('start', start, 'end', end, 'textBefore', textBefore, 'textAfter', textAfter);
  106. // 捕獲被選中的部分並替換它
  107. const selectedText = inputField.value.substring(start, end);
  108. console.log('selectedText', selectedText);
  109. const newText = textBefore + item + textAfter.replace(selectedText, '');
  110. console.log('newText', newText);
  111. inputField.value = newText;
  112. // inputField.value = textBefore + item.replace(selectedText, '') + textAfter;
  113. suggestions.innerHTML = ''; // 清空建議
  114. suggestions.style.border = 'none';
  115. }
  116. }
  117. });
  118. // const start = inputField.selectionStart;
  119. // const end = inputField.selectionEnd;
  120. });
  121. suggestions.appendChild(div);
  122. });
  123. } else {
  124. suggestions.style.border = 'none';
  125. suggestions.style.display = 'none'; // 隱藏建議容器
  126. }
  127. });
  128. });
  129. document.addEventListener('DOMContentLoaded', function () {
  130. // const input = document.getElementById('stockInput');
  131. const suggestions_compare = document.getElementById('suggestions_compare');
  132. inputField_compare.addEventListener('input', function () {
  133. const query = this.value.trim();
  134. suggestions_compare.innerHTML = ''; // 清空之前的建議
  135. console.log('query', query);
  136. if (query) {
  137. // const filteredVocab = vocab.filter(item => item.toLowerCase().includes(query));
  138. const queryTerms = query.split(/\s+/); // 分割輸入的內容
  139. const lastQueryTerm = queryTerms[queryTerms.length - 1];
  140. const filteredVocab = vocab.filter(item => {
  141. // 對每個分割後的單詞進行匹配,只要其中有一個匹配成功就返回 true
  142. return item.toLowerCase().includes(lastQueryTerm.toLowerCase());
  143. });
  144. if (filteredVocab.length > 0) {
  145. suggestions_compare.style.border = '1px solid #ccc';
  146. suggestions_compare.style.display = 'block'; // 顯示建議容器
  147. } else {
  148. suggestions_compare.style.border = 'none';
  149. suggestions_compare.style.display = 'none'; // 隱藏建議容器
  150. }
  151. console.log(queryTerms[queryTerms.length - 1])
  152. filteredVocab.forEach(item => {
  153. const div = document.createElement('div');
  154. div.classList.add('autocomplete-suggestion');
  155. div.textContent = item;
  156. // div.addEventListener('click', function () {
  157. // inputField.value = item;
  158. // suggestions.innerHTML = ''; // 清空建議
  159. // suggestions.style.border = 'none';
  160. // suggestions.style.display = 'none'; // 隱藏建議容器
  161. // });
  162. console.log('queryTerms', queryTerms);
  163. // console.log('filteredVocab', filteredVocab);
  164. div.addEventListener('click', function () {
  165. queryTerms.forEach((term, index) => {
  166. const regex = new RegExp(term, 'gi');
  167. const match = regex.exec(inputField_compare.value);
  168. // console.log(queryTerms[queryTerms.length - 1]);
  169. // console.log(filteredVocab, queryTerms[queryTerms.length - 1])
  170. if (index === queryTerms.length - 1) {
  171. if (match) {
  172. var start = match.index;
  173. var end = start + match[0].length;
  174. console.log(start, end)
  175. const textBefore = inputField_compare.value.substring(0, start);
  176. const textAfter = inputField_compare.value.substring(end, inputField_compare.value.length);
  177. console.log('start', start, 'end', end, 'textBefore', textBefore, 'textAfter', textAfter);
  178. // 捕獲被選中的部分並替換它
  179. const selectedText = inputField_compare.value.substring(start, end);
  180. console.log('selectedText', selectedText);
  181. const newText = textBefore + item + textAfter.replace(selectedText, '');
  182. console.log('newText', newText);
  183. inputField_compare.value = newText;
  184. // inputField.value = textBefore + item.replace(selectedText, '') + textAfter;
  185. suggestions_compare.innerHTML = ''; // 清空建議
  186. suggestions_compare.style.border = 'none';
  187. }
  188. }
  189. });
  190. // const start = inputField.selectionStart;
  191. // const end = inputField.selectionEnd;
  192. });
  193. suggestions_compare.appendChild(div);
  194. });
  195. } else {
  196. suggestions_compare.style.border = 'none';
  197. suggestions_compare.style.display = 'none'; // 隱藏建議容器
  198. }
  199. });
  200. });
  201. // 監聽輸入框的鍵盤事件
  202. // document.getElementById("keyword_data").addEventListener("keyup", function (event) {
  203. // // 判斷是否按下 Enter 鍵 (key code: 13)
  204. // if (event.keyCode === 13) {
  205. // // 觸發送出按鈕的點擊事件
  206. // document.getElementById("send_data").click();
  207. // }
  208. // });
  209. // 定義按鈕點擊事件處理函數
  210. function sendButtonClickHandler() {
  211. // inputField.value = "";
  212. console.log(inputField.value);
  213. var input_text_value = inputField.value;
  214. $('#chartdiv').hide();
  215. sidebar.classList.remove('show');
  216. sidebar.classList.add('hidden');
  217. // 在這裡添加你希望在按下按鈕時執行的其他代碼
  218. get_data(input_text_value);
  219. }
  220. sendButton.addEventListener("click", function () {
  221. sendButtonClickHandler();
  222. inputField.value = ''
  223. });
  224. // sendButton.addEventListener("click", function () {
  225. // console.log(inputField.value);
  226. // input_text_value = inputField.value;
  227. // // if (myChart) {
  228. // // console.log('已存在')
  229. // // data = [];
  230. // // labels = [];
  231. // // myChart.removePlugin(Chart.pluginService.getPlugin('afterDraw'));
  232. // // myChart.destroy();
  233. // // }
  234. // get_data(input_text_value);
  235. // });
  236. var lastKeyPressTime = 0;
  237. var enterCount = 0;
  238. var ENTER_THRESHOLD = 500; // 設定連續按下 Enter 的時間閾值(毫秒)
  239. inputField.addEventListener("keyup", function (event) {
  240. // 判斷是否按下 Enter 鍵 (key code: 13)
  241. if (event.key === "Enter") {
  242. var currentTime = new Date().getTime();
  243. // 計算與上一次按鍵按下時間的差值
  244. var timeDiff = currentTime - lastKeyPressTime;
  245. lastKeyPressTime = currentTime;
  246. // 如果兩次 Enter 鍵按下的時間差小於閾值,則增加計數器
  247. if (timeDiff <= ENTER_THRESHOLD) {
  248. enterCount++;
  249. // 如果計數器為2,則觸發 API 請求並重置計數器
  250. if (enterCount === 2) {
  251. sendButtonClickHandler();
  252. enterCount = 0;
  253. }
  254. } else {
  255. // 如果時間差大於閾值,則重置計數器
  256. enterCount = 0;
  257. }
  258. } else {
  259. // 如果按下的不是 Enter 鍵,則重置計數器
  260. enterCount = 0;
  261. }
  262. });
  263. function number_format(number, decimals, dec_point, thousands_sep) {
  264. // * example: number_format(1234.56, 2, ',', ' ');
  265. // * return: '1 234,56'
  266. number = (number + '').replace(',', '').replace(' ', '');
  267. var n = !isFinite(+number) ? 0 : +number,
  268. prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
  269. sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
  270. dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
  271. s = '',
  272. toFixedFix = function (n, prec) {
  273. var k = Math.pow(10, prec);
  274. return '' + Math.round(n * k) / k;
  275. };
  276. // Fix for IE parseFloat(0.55).toFixed(0) = 0;
  277. s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
  278. if (s[0].length > 3) {
  279. s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
  280. }
  281. if ((s[1] || '').length < prec) {
  282. s[1] = s[1] || '';
  283. s[1] += new Array(prec - s[1].length + 1).join('0');
  284. }
  285. return s.join(dec);
  286. }
  287. var labels = [];
  288. var data = [];
  289. function apiLoading() {
  290. document.getElementById('chartDataLoading').style.display = 'block';
  291. }
  292. function apiHideLoading() {
  293. document.getElementById('chartDataLoading').style.display = 'none';
  294. }
  295. const DISPLAY = true;
  296. const BORDER = true;
  297. const CHART_AREA = true;
  298. const TICKS = true;
  299. var labels = [];
  300. var data = [];
  301. var dataArray;
  302. function generateChart(dataArray) {
  303. if (dataArray.length > 10) {
  304. lineYfontSize.style.display = 'none'
  305. } else {
  306. lineYfontSize.style.display = 'block'
  307. }
  308. // 创建一个空数组来存储 labels 和 data
  309. console.log(dataArray)
  310. // 遍历数据数组
  311. for (var i = 0; i < dataArray.length; i++) {
  312. var item = dataArray[i];
  313. // 遍历当前项的属性
  314. for (var key in item) {
  315. // 如果属性名不在 labels 数组中,并且属性值不是对象,则将属性名添加到 labels 数组中
  316. if (typeof item[key] === 'string') {
  317. labels.push(item[key]);
  318. }
  319. // 如果属性值是数值类型,则将其添加到 data 数组中
  320. if (typeof item[key] === 'number') {
  321. data.push(item[key]);
  322. }
  323. }
  324. }
  325. console.log(data);
  326. if (myChart) {
  327. console.log('已存在')
  328. myChart.destroy();
  329. }
  330. if (chartType === "doughnut") {
  331. // createChartDoughnut(chartType, data, labels);
  332. // createChartDoughnut2(data, labels)
  333. } else {
  334. createChart(chartType, data, labels);
  335. }
  336. // console.log(data);
  337. console.log(labels);
  338. }
  339. var amChartInstance;
  340. function extractAndGenerateChart(dataArray) {
  341. for (var i = 0; i < dataArray.length; i++) {
  342. var item = dataArray[i];
  343. // 遍历当前项的属性
  344. for (var key in item) {
  345. // 如果属性名不在 labels 数组中,并且属性值不是对象,则将属性名添加到 labels 数组中
  346. if (typeof item[key] === 'string') {
  347. labels.push(item[key]);
  348. }
  349. // 如果属性值是数值类型,则将其添加到 data 数组中
  350. if (typeof item[key] === 'number') {
  351. data.push(item[key]);
  352. }
  353. }
  354. }
  355. // console.log('key', key);
  356. generateBarChart(key, dataArray);
  357. }
  358. // 圓餅圖
  359. function generatePieChart(dataArray) {
  360. $('#chartdiv').show();
  361. bgImgelement.style.backgroundImage = chart_bgimg_url;
  362. // chartDiv.style.width = "100%"
  363. chartWhitelement.style.background = "rgba(255,255,255,0.5)"
  364. // Themes begin
  365. am4core.useTheme(am4themes_animated);
  366. // Themes end
  367. // Create chart instance
  368. var chart = am4core.create("chartdiv", am4charts.PieChart);
  369. chart.data = dataArray;
  370. chart.innerRadius = am4core.percent(50);
  371. // Add and configure Series
  372. var pieSeries = chart.series.push(new am4charts.PieSeries());
  373. pieSeries.dataFields.value = "y";
  374. pieSeries.dataFields.category = "x";
  375. pieSeries.slices.template.stroke = am4core.color("#fff");
  376. pieSeries.slices.template.strokeWidth = 2;
  377. pieSeries.slices.template.strokeOpacity = 1;
  378. // Configure labels
  379. pieSeries.labels.template.wrap = true;
  380. pieSeries.labels.template.maxWidth = 100;
  381. pieSeries.labels.template.truncate = false;
  382. pieSeries.labels.template.fontSize = 14;
  383. // Configure label text to wrap and show percentages
  384. pieSeries.labels.template.adapter.add("textOutput", function (text, target) {
  385. if (target.dataItem && target.dataItem.values.value.percent) {
  386. return "[font-size: 14px]" + target.dataItem.category + ": " + target.dataItem.values.value.percent.toFixed(1) + "%";
  387. }
  388. return text;
  389. });
  390. pieSeries.colors.list = [
  391. am4core.color('rgb(171, 51, 49)'),
  392. am4core.color('rgb(34, 83, 149)'),
  393. am4core.color('rgb(79, 148, 65)'),
  394. am4core.color('rgb(217, 195, 105)'),
  395. am4core.color('rgb(142, 124, 180)'),
  396. am4core.color('rgb(211, 183, 144)'),
  397. am4core.color('rgb(83, 84, 84)'),
  398. am4core.color('rgb(229, 147, 152)')
  399. ];
  400. // Ensure labels have background
  401. // pieSeries.labels.template.background.fillOpacity = 1;
  402. console.log(pieSeries.labels.template)
  403. // Set label background color to match corresponding slice color
  404. pieSeries.labels.template.adapter.add("background.fill", function (fill, target) {
  405. return target.dataItem.slice.fill;
  406. });
  407. // This creates initial animation
  408. pieSeries.hiddenState.properties.opacity = 1;
  409. pieSeries.hiddenState.properties.endAngle = -90;
  410. pieSeries.hiddenState.properties.startAngle = -90;
  411. // console.log('調位置')
  412. downloadButton.style.display = "inline-block";
  413. }
  414. // var colorEven='#288D97';
  415. // var colorOdd='#427D7E'
  416. function adjustColorBrightness(color, amount) {
  417. let usePound = false;
  418. if (color[0] == "#") {
  419. color = color.slice(1);
  420. usePound = true;
  421. }
  422. let num = parseInt(color, 16);
  423. let r = (num >> 16) + amount;
  424. let g = ((num >> 8) & 0x00FF) + amount;
  425. let b = (num & 0x0000FF) + amount;
  426. if (r > 255) r = 255;
  427. else if (r < 0) r = 0;
  428. if (g > 255) g = 255;
  429. else if (g < 0) g = 0;
  430. if (b > 255) b = 255;
  431. else if (b < 0) b = 0;
  432. return (usePound ? "#" : "") + (r << 16 | g << 8 | b).toString(16).padStart(6, '0');
  433. }
  434. // 柱狀圖
  435. function generateBarChart(key, dataArray) {
  436. $('#chartdiv').show();
  437. // var colorEven = document.getElementById('colorEven').value;
  438. // var colorOdd = document.getElementById('colorOdd').value;
  439. var baseColor = document.getElementById('borderColorInputBar').value;
  440. var categoryAxisFzValue = document.getElementById('XfontSizeBar').value;
  441. var valueAxisFzValue = document.getElementById('YfontSizeBar').value;
  442. var colorEven = am4core.color(baseColor);
  443. var colorOdd = adjustColorBrightness(baseColor, -20);
  444. chartDiv.style.fontFamily = "Arial, sans-serif";
  445. chartDiv.style.fontWeight = "900";
  446. bgImgelement.style.backgroundImage = chart_bgimg_url;
  447. chartWhitelement.style.background = "rgba(255,255,255,0.5)"
  448. console.log('labelDependent', labelDependent);
  449. // Themes begin
  450. am4core.useTheme(am4themes_animated);
  451. // Themes end
  452. // Create chart instance
  453. var chart = am4core.create("chartdiv", am4charts.XYChart3D);
  454. console.log('判斷資料', dataArray);
  455. chart.data = dataArray;
  456. chart.angle = 30; // 圖表角度
  457. chart.depth = 25; // 圖表深度
  458. let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
  459. categoryAxis.dataFields.category = "x";
  460. categoryAxis.renderer.labels.template.rotation = 0;
  461. categoryAxis.renderer.labels.template.hideOversized = false;
  462. categoryAxis.renderer.minGridDistance = 20; // 間距
  463. // categoryAxis.renderer.labels.template.horizontalCenter = "left";
  464. // categoryAxis.renderer.labels.template.verticalCenter = "middle";
  465. // categoryAxis.tooltip.label.rotation = 90;
  466. // categoryAxis.tooltip.label.horizontalCenter = "right";
  467. // categoryAxis.tooltip.label.verticalCenter = "middle";
  468. // label 換行
  469. let label = categoryAxis.renderer.labels.template;
  470. label.wrap = true;
  471. if (dataArray.length > 5) {
  472. console.log('資料長度5')
  473. label.maxWidth = 50;
  474. } else {
  475. label.maxWidth = 100;
  476. console.log('資料長度<5')
  477. }
  478. // x軸字體大小
  479. categoryAxis.renderer.labels.template.fontSize = categoryAxisFzValue;
  480. categoryAxis.renderer.cellEndLocation = 0.5; // 減少值可以增加間距
  481. let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  482. valueAxis.title.text = labelDependent; // Y軸
  483. valueAxis.title.fontWeight = "bold";
  484. // y軸字體大小
  485. valueAxis.renderer.labels.template.fontSize = valueAxisFzValue;
  486. // Create series
  487. var series = chart.series.push(new am4charts.ColumnSeries3D());
  488. series.dataFields.valueY = 'y';
  489. series.dataFields.categoryX = "x";
  490. series.name = "y";
  491. series.tooltipText = "{categoryX}: [bold]{valueY}[/]";
  492. series.columns.template.fillOpacity = .8;
  493. let columnTemplate = series.columns.template;
  494. columnTemplate.strokeWidth = 2;
  495. columnTemplate.strokeOpacity = 1;
  496. columnTemplate.stroke = am4core.color("#FFFFFF");
  497. // columnTemplate.adapter.add("fill", function (fill, target) {
  498. // return chart.colors.getIndex(target.dataItem.index);
  499. // })
  500. // 設置基數和偶數顏色
  501. columnTemplate.adapter.add("fill", function (fill, target) {
  502. return target.dataItem.index % 2 === 0 ? am4core.color(colorEven) : am4core.color(colorOdd);
  503. });
  504. columnTemplate.adapter.add("stroke", function (stroke, target) {
  505. return chart.colors.getIndex(target.dataItem.index);
  506. })
  507. // // 加上白色線條
  508. // series.columns.template.events.on("validated", function (event) {
  509. // let column = event.target;
  510. // let whiteLine = column.createChild(am4core.Line);
  511. // whiteLine.stroke = am4core.color("#ffffff");
  512. // whiteLine.strokeWidth = 1.3; // 線條寬度
  513. // whiteLine.strokeOpacity = 0.5;// 透明度
  514. // whiteLine.x1 = column.pixelWidth / 1;
  515. // whiteLine.y1 = 0;
  516. // whiteLine.x2 = column.pixelWidth / 1;
  517. // whiteLine.y2 = column.pixelHeight;
  518. // });
  519. chart.cursor = new am4charts.XYCursor();
  520. chart.cursor.lineX.strokeOpacity = 0;
  521. chart.cursor.lineY.strokeOpacity = 0;
  522. categoryAxis.renderer.grid.template.strokeOpacity = 0; // 隱藏 X 軸
  523. // valueAxis.renderer.grid.template.strokeOpacity = 0; // 隱藏 Y 軸
  524. // valueAxis.renderer.line.strokeOpacity = 0;
  525. // LineSeries 底部線條
  526. var lineSeries = chart.series.push(new am4charts.LineSeries());
  527. lineSeries.dataFields.valueY = "bottom";
  528. lineSeries.dataFields.categoryX = "0";
  529. lineSeries.stroke = am4core.color("#FFFFFF")
  530. lineSeries.strokeWidth = 2;
  531. lineSeries.strokeOpacity = 1
  532. var bottomData = [];
  533. for (var i = 0; i < dataArray.length; i++) {
  534. bottomData.push({ "x": dataArray[i].x, "bottom": 0 });
  535. }
  536. lineSeries.data = bottomData;
  537. downloadButton.style.display = "inline-block";
  538. // 底部線條
  539. // let rectElements = document.querySelectorAll("rect");
  540. // // let widthValue = 0;
  541. // let fifthRectElement = rectElements[3];
  542. // // fifthRectElement.style.position = "relative";
  543. // setTimeout(() => {
  544. // let width = fifthRectElement.getAttribute("width");
  545. // // widthValue = parseFloat(width);
  546. // console.log('rectElements', rectElements);
  547. // console.log('fifthRectElement', fifthRectElement);
  548. // console.log('width', width);
  549. // let line = document.createElement("div");
  550. // line.style.position = "absolute";
  551. // line.style.bottom = "162px";
  552. // line.style.right = "40px";
  553. // line.style.width = `${parseFloat(width)}px`;
  554. // line.style.height = "2px";
  555. // line.style.backgroundColor = "#000000";
  556. // document.getElementById("chartdiv").appendChild(line);
  557. // }, 10)
  558. // end am4core.ready()
  559. }
  560. var downloadButton = document.getElementById("downloadButton");
  561. // downloadButton.style.display = 'none';
  562. // 添加點擊事件監聽器
  563. downloadButton.addEventListener('click', function () {
  564. setTimeout(function () {
  565. html2canvas(document.getElementById('data_chart_box')).then(function (canvas) {
  566. // // 獲取圖片資料 URL
  567. // var imageData = canvasPng.toDataURL("image/png");
  568. // // 創建下載連結
  569. // var link = document.createElement('a');
  570. // link.download = 'chart.png'; // 下載的檔案名稱
  571. // link.href = imageData; // 圖片資料 URL
  572. // // 模擬點擊下載連結
  573. // link.click();
  574. // 創建一個&lt;a&gt;元素
  575. const link = document.createElement('a');
  576. // 將 Canvas 轉換為 Data URL
  577. link.href = canvas.toDataURL('image/png');
  578. // 設置下載文件名
  579. link.download = 'chart.png';
  580. // 模擬點擊下載
  581. link.click();
  582. });
  583. }, 1000); // 等待 1 秒钟
  584. });
  585. var pieChartColors = [
  586. 'rgb(171, 51, 49)',
  587. 'rgb(34, 83, 149)',
  588. 'rgb(79, 148, 65)',
  589. 'rgb(217, 195, 105)',
  590. 'rgb(142, 124, 180)',
  591. 'rgb(211, 183, 144)',
  592. 'rgb(83, 84, 84)',
  593. 'rgb(229, 147, 152)'
  594. ];
  595. function createChartDoughnut2(data, labels) {
  596. const chartData = {
  597. type: 'outlabeledPie',
  598. data: {
  599. labels: labels,
  600. datasets: [
  601. {
  602. data: data,
  603. borderWidth: borderWidthValue,
  604. fill: false,
  605. backgroundColor: pieChartColors,
  606. },
  607. ],
  608. },
  609. options: {
  610. plugins: {
  611. legend: false,
  612. outlabels: {
  613. text: '%l %p',
  614. color: 'white',
  615. stretch: 35,
  616. font: {
  617. resizable: true,
  618. family: 'Arial',
  619. size: 48,
  620. weight: 'bold',
  621. style: 'italic',
  622. },
  623. },
  624. },
  625. },
  626. };
  627. const devicePixelRatio = 4;
  628. var chartUrl = `https://quickchart.io/chart?c=${encodeURIComponent(JSON.stringify(chartData))}&devicePixelRatio=${devicePixelRatio}&f=png&fontsize=20`;
  629. // 获取目标 div 元素
  630. const targetDiv = document.getElementById('data_chartJs_box');
  631. // 创建一个 img 元素来显示图表
  632. const chartImg = document.createElement('img');
  633. chartImg.src = chartUrl;
  634. chartImg.classList.add('outlabeled_img');
  635. // 清空目标 div 元素
  636. targetDiv.innerHTML = '';
  637. // 将图表 img 元素插入到目标 div 中
  638. targetDiv.appendChild(chartImg);
  639. console.log(chart_bgimg_url);
  640. bgImgelement.style.backgroundImage = chart_bgimg_url;
  641. chartWhitelement.style.background = "rgba(255,255,255,0.5)"
  642. }
  643. // 版型二源餅圖
  644. // // 创建 Chart.js 图表
  645. function createChartDoughnut(chartType, data, labels) {
  646. $('#textToChart').show();
  647. canvasPng.style.maxWidth = '450px';
  648. canvasPng.style.maxHeight = '450px';
  649. var windowWidth = window.innerWidth || document.documentElement.clientWidth;
  650. var canvasWidth = Math.min(500, windowWidth); // 限制最大宽度为 500px
  651. canvasPng.style.width = canvasWidth + 'px'; // 设置 canvas 宽度
  652. canvasPng.style.marginLeft = 'auto'; // 左外边距自动计算
  653. canvasPng.style.marginRight = 'auto'; // 右外边距自动计算
  654. var ctx = document.getElementById("textToChart").getContext('2d');
  655. myChart = new Chart(ctx, {
  656. type: chartType,
  657. data: {
  658. labels: labels,
  659. datasets: [{
  660. strokeColor: "rgba(220,220,220,1)",
  661. StrokeWidth: 5,
  662. data: data,
  663. borderWidth: borderWidthValue,
  664. pointRadius: 0,
  665. pointBorderColor: '#fff',
  666. pointBorderWidth: 3,
  667. pointHoverRadius: 0,
  668. fill: false,
  669. backgroundColor: pieChartColors,
  670. // barPercentage: 0.5,
  671. // categoryPercentage: 0.5
  672. }]
  673. },
  674. options: {
  675. // responsive: true, // 啟用響應式
  676. maintainAspectRatio: true,
  677. responsive: true,
  678. aspectRatio: 1,
  679. plugins: {
  680. legend: {
  681. display: false,
  682. },
  683. datalabels: {
  684. color: '#fff', // 数据标签的颜色
  685. anchor: 'end', // 数据标签的位置
  686. align: 'start', // 数据标签的对齐方式
  687. font: {
  688. size: 14, // 数据标签的字体大小
  689. },
  690. }
  691. },
  692. scales: {
  693. x: {
  694. display: false, // 显示 x 轴
  695. grid: {
  696. display: false,
  697. lineWidth: 5,
  698. color: '#584B3D'
  699. },
  700. ticks: {
  701. textStrokeColor: '#fff',
  702. textStrokeWidth: 5,
  703. font: {
  704. display: false,
  705. family: 'Arial', // 设置字体
  706. size: XfontSizeValue, // 设置字体大小
  707. weight: 'bold', // 设置字体粗细
  708. fontColor: 'rgba(255,255,255,0.8)', // X-axis font color
  709. shadowColor: 'rgba(0, 0, 0, 0.5)', // Shadow color
  710. shadowBlur: 10, // Shadow blur level
  711. shadowOffsetX: 5, // Horizontal shadow offset
  712. shadowOffsetY: 5
  713. },
  714. },
  715. },
  716. y: {
  717. display: false,
  718. beginAtZero: false,
  719. grid: {
  720. display: false,
  721. lineWidth: 5,
  722. color: '#584B3D'
  723. },
  724. ticks: {
  725. textStrokeColor: '#fff',
  726. textStrokeWidth: 5,
  727. font: {
  728. family: 'Arial', // 設置字體
  729. size: XfontSizeValue, // 設置字體大小
  730. weight: 'bold', // 設置字體粗細
  731. color: 'rgba(255,255,255,0.8)', // Y-axis 字體顏色
  732. shadowColor: 'rgba(0, 0, 0, 0.5)', // 陰影顏色
  733. shadowBlur: 10, // 陰影模糊級別
  734. shadowOffsetX: 5, // 水平陰影偏移
  735. shadowOffsetY: 5 // 垂直陰影偏移
  736. },
  737. }
  738. }
  739. },
  740. layout: {
  741. padding: {
  742. left: 50, // 调整图表左边距
  743. right: 50, // 调整图表右边距
  744. top: 10,
  745. bottom: 10,
  746. }
  747. },
  748. },
  749. });
  750. console.log(chart_bgimg_url);
  751. bgImgelement.style.backgroundImage = chart_bgimg_url;
  752. chartWhitelement.style.background = "rgba(255,255,255,0.5)"
  753. }
  754. var chartJsBox = document.getElementById('data_chartJs_box');
  755. function createTable(dataArray, unit, Label_dependent_variable, Label_independent_variable,) {
  756. $('#chartdiv').hide();
  757. $('#textToChart').hide();
  758. chartTitle.style.padding = "0px 0px 0px 30px";
  759. bgImgelement.style.paddingTop = "10px";
  760. var table = document.createElement('table');
  761. table.classList.add('table', 'table_template', 'dynamic-table'); // 添加 Bootstrap 的 table 样式,如果你在使用 Bootstrap
  762. // 创建表头
  763. var thead = table.createTHead();
  764. var headerRow = thead.insertRow();
  765. // Object.keys(dataArray[0]).forEach(function (key) {
  766. // var header = headerRow.insertCell();
  767. // header.textContent = key;
  768. // });
  769. var yearHeader = headerRow.insertCell();
  770. yearHeader.textContent = Label_independent_variable;
  771. var avgPriceHeader = headerRow.insertCell();
  772. avgPriceHeader.textContent = Label_dependent_variable;
  773. // 创建表格主体
  774. var tbody = table.createTBody();
  775. dataArray.forEach(function (item) {
  776. console.log(item)
  777. var row = tbody.insertRow();
  778. // var cell1 = row.insertCell();
  779. // cell1.textContent = item.year;
  780. // cell1.classList.add('year-class'); // 为年份单元格添加类
  781. // var cell2 = row.insertCell();
  782. Object.keys(item).forEach(function (key, index) {
  783. var cell = row.insertCell();
  784. if (index === 0) { // If it's the first cell, add 'year-class'
  785. cell.textContent = item[key];
  786. cell.classList.add('year-class');
  787. } else { // Otherwise, add 'average-class'
  788. if (key === 'Average_Close') {
  789. // cell.textContent = item[key].toFixed(2) + unit;
  790. cell.innerHTML = item[key].toFixed(2) + '<span class="unit_table">' + unit + '</span>';
  791. } else {
  792. // cell.textContent = item[key];
  793. // cell.textContent = item[key].toFixed(2) + unit;
  794. cell.innerHTML = item[key].toFixed(2) + '<span class="unit_table">' + unit + '</span>';
  795. }
  796. cell.classList.add('average-class');
  797. }
  798. });
  799. // console.log(item.avg_price.toFixed(2))
  800. // cell.textContent = item[key].toFixed(2) + unit;
  801. // cell2.classList.add('average-class'); // 为年份单元格添加类
  802. });
  803. // 获取包含表格的 div 元素
  804. bgImgelement.style.backgroundImage = chart_bgimg_url;
  805. img_box.style.bottom = '60px';
  806. img_box.style.right = '5px';
  807. document.getElementById('img_box_url').style.width = "180px";
  808. // chartWhitelement.style.background = "rgba(255,255,255,0.5)"
  809. // 将表格添加到指定的 div 元素中
  810. chartJsBox.appendChild(table);
  811. unit_table_value = document.querySelectorAll('.unit_table');
  812. downloadButton.style.display = "inline-block";
  813. }
  814. function getDatalabelsConfig(dataLength) {
  815. if (dataLength < 10) {
  816. return {
  817. formatter: function (value, context) {
  818. return value + ' unit'; // Add unit if data length is less than 10
  819. },
  820. textStrokeColor: '#fff',
  821. textStrokeWidth: 5,
  822. color: '#584B3D',
  823. font: {
  824. size: YfontSizeValue,
  825. },
  826. anchor: 'end',
  827. align: 'end',
  828. };
  829. } else {
  830. return null; // No datalabels if data length is 10 or more
  831. }
  832. }
  833. // 版型一折線圖
  834. // // 创建 Chart.js 图表
  835. function createChart(chartType, data, labels) {
  836. $('#textToChart').show();
  837. canvasPng.style.maxWidth = '';
  838. canvasPng.style.maxHeight = '';
  839. const datalabelsConfig = data.length <= 10 ? {
  840. formatter: function (value, context) {
  841. return value;
  842. },
  843. textStrokeColor: '#fff',
  844. textStrokeWidth: 5,
  845. color: '#584B3D',
  846. font: {
  847. size: YfontSizeValue,
  848. },
  849. anchor: 'end',
  850. align: 'top'
  851. } : null;
  852. ctx = document.getElementById("textToChart");
  853. myChart = new Chart(ctx, {
  854. type: chartType,
  855. data: {
  856. labels: labels,
  857. datasets: [{
  858. strokeColor: "rgba(220,220,220,1)",
  859. StrokeWidth: 5,
  860. data: data,
  861. borderWidth: borderWidthValue,
  862. pointRadius: 0,
  863. pointBorderColor: '#fff',
  864. pointBorderWidth: 3,
  865. pointHoverRadius: 0,
  866. fill: false,
  867. backgroundColor: chartColor,
  868. borderColor: chartColor,
  869. // barPercentage: 0.5,
  870. // categoryPercentage: 0.5
  871. }]
  872. },
  873. options: {
  874. // responsive: true, // 啟用響應式
  875. maintainAspectRatio: false,
  876. plugins: {
  877. chart3d: {
  878. enabled: true, // 啟用 3D 插件
  879. alpha: 25, // 圖表繞 x 軸的旋轉角度
  880. beta: 25, // 圖表繞 y 軸的旋轉角度
  881. depth: 15 // 圖表的深度
  882. },
  883. datalabels: datalabelsConfig,
  884. // datalabels: {
  885. // formatter: function (value, context) {
  886. // // 添加单位
  887. // return value;
  888. // },
  889. // textStrokeColor: '#fff',
  890. // textStrokeWidth: 5,
  891. // color: '#584B3D',
  892. // font: {
  893. // size: YfontSizeValue,
  894. // },
  895. // anchor: 'end',
  896. // align: 'end',
  897. // },
  898. // datalabels: null,
  899. // datalabels: data.length <= 10 ? {
  900. // formatter: function (value, context) {
  901. // // 添加单位
  902. // return value + ' ' + unit;
  903. // },
  904. // textStrokeColor: '#fff',
  905. // textStrokeWidth: 5,
  906. // color: '#584B3D',
  907. // font: {
  908. // size: YfontSizeValue,
  909. // },
  910. // anchor: 'end',
  911. // align: 'end',
  912. // } : null,
  913. customLabels: null,
  914. legend: {
  915. display: false,
  916. },
  917. // subtitle: {
  918. // display: true,
  919. // align: 'left',
  920. // position: 'top',
  921. // text: 'Custom Chart Subtitle'
  922. // },
  923. // afterDatasetsDraw(chart) {
  924. // var ctx = chart.ctx;
  925. // var xAxis = chart.scales.x;
  926. // var yAxis = chart.scales.y;
  927. // var dataj = myChart.data.datasets[0].data;
  928. // console.log('after', dataj)
  929. // ctx.save();
  930. // ctx.strokeStyle = '#000'; // 虚线颜色
  931. // ctx.lineWidth = 1; // 虚线宽度
  932. // ctx.setLineDash([5, 5]); // 设置虚线样式,[虚线段长度, 间隔长度]
  933. // dataj.forEach(function (value, index) {
  934. // var xPos = xAxis.getPixelForValue(labels[index]); // 获取 x 轴位置
  935. // var yPos = yAxis.getPixelForValue(value); // 获取 y 轴位置
  936. // // console.log(xPos)
  937. // // 绘制虚线
  938. // // ctx.beginPath();
  939. // // ctx.moveTo(xPos, yPos);
  940. // // ctx.lineTo(xPos, xAxis.bottom);
  941. // // ctx.stroke();
  942. // var xLineEnd = Math.min(yPos, yAxis.bottom);
  943. // // console.log('第' + index + '條線', xPos, yPos, 'xLineEnd', xLineEnd, 'yAxis.getPixelForValue(value)', yAxis.getPixelForValue(value));
  944. // // console.log('yAxis.getPixelForValue(value)', yAxis.getPixelForValue(value))
  945. // // 绘制虚线
  946. // ctx.beginPath();
  947. // ctx.moveTo(xPos, yAxis.getPixelForValue(value));
  948. // ctx.lineTo(xPos, xAxis.top);
  949. // ctx.stroke();
  950. // // 添加日期标签
  951. // ctx.fillStyle = '#000'; // 文字颜色
  952. // ctx.textAlign = 'center';
  953. // // ctx.fillText(labels[index], xPos, yAxis.bottom + 20); // 将日期标签绘制在虚线下方
  954. // });
  955. // ctx.restore();
  956. // }
  957. },
  958. scales: {
  959. x: {
  960. display: displayXaxes, // 显示 x 轴
  961. grid: {
  962. display: false,
  963. lineWidth: 5,
  964. color: '#584B3D'
  965. },
  966. ticks: {
  967. textStrokeColor: '#fff',
  968. textStrokeWidth: 5,
  969. font: {
  970. display: false,
  971. family: 'Arial', // 设置字体
  972. size: XfontSizeValue, // 设置字体大小
  973. weight: 'bold', // 设置字体粗细
  974. fontColor: 'rgba(255,255,255,0.8)', // X-axis font color
  975. shadowColor: 'rgba(0, 0, 0, 0.5)', // Shadow color
  976. shadowBlur: 10, // Shadow blur level
  977. shadowOffsetX: 5, // Horizontal shadow offset
  978. shadowOffsetY: 5
  979. },
  980. },
  981. },
  982. y: {
  983. display: true,
  984. beginAtZero: false,
  985. grid: {
  986. grid: {
  987. display: true, // 显示 y 轴网格线
  988. lineWidth: 1, // 网格线宽度
  989. color: '#C0C5CC' // 网格线颜色
  990. },
  991. },
  992. ticks: {
  993. textStrokeColor: '#fff',
  994. textStrokeWidth: 5,
  995. font: {
  996. family: 'Arial', // 設置字體
  997. size: XfontSizeValue, // 設置字體大小
  998. weight: 'bold', // 設置字體粗細
  999. color: 'rgba(255,255,255,0.8)', // Y-axis 字體顏色
  1000. shadowColor: 'rgba(0, 0, 0, 0.5)', // 陰影顏色
  1001. shadowBlur: 10, // 陰影模糊級別
  1002. shadowOffsetX: 5, // 水平陰影偏移
  1003. shadowOffsetY: 5 // 垂直陰影偏移
  1004. },
  1005. }
  1006. }
  1007. },
  1008. layout: {
  1009. padding: {
  1010. left: 50, // 调整图表左边距
  1011. right: 50, // 调整图表右边距
  1012. top: 40,
  1013. bottom: 10,
  1014. }
  1015. },
  1016. },
  1017. });
  1018. // 注册 afterDraw 事件
  1019. // Chart.register({
  1020. // id: 'afterDraw',
  1021. // afterDraw: function (chart) {
  1022. // // console.log('處發', chart)
  1023. // // 在这里添加你的绘制代码
  1024. // var ctx = chart.ctx;
  1025. // var xAxis = chart.scales.x;
  1026. // var yAxis = chart.scales.y;
  1027. // var dataj = myChart.data.datasets[0].data;
  1028. // console.log('after', dataj)
  1029. // ctx.save();
  1030. // ctx.strokeStyle = '#000'; // 虚线颜色
  1031. // ctx.lineWidth = 1; // 虚线宽度
  1032. // ctx.setLineDash([5, 5]); // 设置虚线样式,[虚线段长度, 间隔长度]
  1033. // dataj.forEach(function (value, index) {
  1034. // var xPos = xAxis.getPixelForValue(labels[index]); // 获取 x 轴位置
  1035. // var yPos = yAxis.getPixelForValue(value); // 获取 y 轴位置
  1036. // // console.log(xPos)
  1037. // // 绘制虚线
  1038. // // ctx.beginPath();
  1039. // // ctx.moveTo(xPos, yPos);
  1040. // // ctx.lineTo(xPos, xAxis.bottom);
  1041. // // ctx.stroke();
  1042. // var xLineEnd = Math.min(yPos, yAxis.bottom);
  1043. // // console.log('第' + index + '條線', xPos, yPos, 'xLineEnd', xLineEnd, 'yAxis.getPixelForValue(value)', yAxis.getPixelForValue(value));
  1044. // // console.log('yAxis.getPixelForValue(value)', yAxis.getPixelForValue(value))
  1045. // // 绘制虚线
  1046. // ctx.beginPath();
  1047. // ctx.moveTo(xPos, yAxis.getPixelForValue(value));
  1048. // ctx.lineTo(xPos, xAxis.top);
  1049. // ctx.stroke();
  1050. // // 添加日期标签
  1051. // ctx.fillStyle = '#000'; // 文字颜色
  1052. // ctx.textAlign = 'center';
  1053. // // ctx.fillText(labels[index], xPos, yAxis.bottom + 20); // 将日期标签绘制在虚线下方
  1054. // });
  1055. // ctx.restore();
  1056. // }
  1057. // });
  1058. console.log(chart_bgimg_url);
  1059. bgImgelement.style.backgroundImage = chart_bgimg_url;
  1060. chartWhitelement.style.background = "rgba(255,255,255,0.5)";
  1061. downloadButton.style.display = "inline-block";
  1062. }
  1063. // createChart(chartType);
  1064. // createChart(chartType)
  1065. const ChartOptions = document.querySelectorAll('input[name="ChartOptions"]');
  1066. ChartOptions.forEach(button => {
  1067. button.addEventListener('click', function () {
  1068. // console.log(this.value);
  1069. chartType = this.value;
  1070. console.log(chartType);
  1071. if (myChart !== null) {
  1072. myChart.destroy();
  1073. data = [];
  1074. labels = [];
  1075. }
  1076. if (chartType === 'line') {
  1077. $('#chartdiv').hide();
  1078. generateChart(dataArray);
  1079. } else {
  1080. $('#textToChart').hide();
  1081. extractAndGenerateChart(dataArray);
  1082. }
  1083. // createChart(chartType, data, labels);
  1084. });
  1085. });
  1086. // 表格字體
  1087. var fontSizeInput = document.getElementById('table_fontSize');
  1088. // 添加事件监听器
  1089. fontSizeInput.addEventListener('input', function () {
  1090. // 获取输入框的值
  1091. var fontSize = fontSizeInput.value;
  1092. // 获取动态创建的表格元素
  1093. var dynamicTable = document.querySelector('.dynamic-table');
  1094. // 如果表格存在,则调整其字体大小
  1095. if (dynamicTable) {
  1096. dynamicTable.style.fontSize = fontSize + 'px';
  1097. }
  1098. });
  1099. // 是否顯示point
  1100. var togglePoints = document.getElementById('togglePoints');
  1101. togglePoints.addEventListener('change', function () {
  1102. console.log('checked')
  1103. if (this.checked) {
  1104. myChart.data.datasets[0].pointRadius = 5;
  1105. myChart.data.datasets[0].pointHoverRadius = 8;
  1106. } else {
  1107. myChart.data.datasets[0].pointRadius = 0;
  1108. myChart.data.datasets[0].pointHoverRadius = 0;
  1109. }
  1110. myChart.update();
  1111. });
  1112. // 單位
  1113. // =========================
  1114. var unitInput = document.getElementById('unit_data');
  1115. var unit_value = document.querySelector('.unit');
  1116. // console.log('單位', unitInput)
  1117. unitInput.addEventListener('input', function () {
  1118. // unitInput.value = unit_value.textContent;
  1119. if (chartType === 'table') {
  1120. // unit_table_value.textContent = this.value;
  1121. unit_table_value.forEach(span => {
  1122. span.textContent = this.value;
  1123. });
  1124. } else {
  1125. unit_value.textContent = '單位:' + this.value
  1126. }
  1127. });
  1128. // ==========================
  1129. // y軸字體大小-折線圖
  1130. // ==========================
  1131. var YfontSize = document.getElementById('YfontSize');
  1132. YfontSize.addEventListener('input', function () {
  1133. YfontSizeValue = YfontSize.value;
  1134. if (myChart) {
  1135. myChart.destroy();
  1136. }
  1137. createChart(chartType, data, labels);
  1138. });
  1139. // ==========================
  1140. // x軸字體大小-折線圖
  1141. // ==========================
  1142. var XfontSize = document.getElementById('XfontSize');
  1143. XfontSize.addEventListener('input', function () {
  1144. XfontSizeValue = XfontSize.value;
  1145. console.log(XfontSizeValue)
  1146. if (myChart) {
  1147. myChart.destroy();
  1148. }
  1149. createChart(chartType, data, labels);
  1150. });
  1151. // ==========================
  1152. // x軸字體大小-柱狀圖
  1153. // ==========================
  1154. var XfontSizeBar = document.getElementById('XfontSizeBar');
  1155. XfontSizeBar.addEventListener('input', function () {
  1156. extractAndGenerateChart(dataArray);
  1157. });
  1158. // ==========================
  1159. // y軸字體大小-柱狀圖
  1160. // ==========================
  1161. var YfontSizeBar = document.getElementById('YfontSizeBar');
  1162. YfontSizeBar.addEventListener('input', function () {
  1163. extractAndGenerateChart(dataArray);
  1164. });
  1165. // ==========================
  1166. // 線條顏色-柱狀圖
  1167. // =========================
  1168. const colorInputBar = document.getElementById('borderColorInputBar');
  1169. // 添加事件监听器,当颜色选择发生变化时触发
  1170. colorInputBar.addEventListener('input', function () {
  1171. extractAndGenerateChart(dataArray);
  1172. });
  1173. // =========================
  1174. // 線條粗細
  1175. // =========================
  1176. var rangeInput = document.getElementById('borderWidthRange');
  1177. var rangeInputSpan = document.getElementById('borderWidthRangeValue');
  1178. var borderWidthValue;
  1179. rangeInputSpan.textContent = rangeInput.value;
  1180. // 添加事件监听器,当滑动条的值发生改变时更新显示的值
  1181. rangeInput.addEventListener('input', function () {
  1182. // 获取滑动条的当前值
  1183. borderWidthValue = rangeInput.value;
  1184. console.log(borderWidthValue);
  1185. rangeInputSpan.textContent = borderWidthValue;
  1186. if (myChart) {
  1187. myChart.destroy();
  1188. }
  1189. createChart(chartType, data, labels);
  1190. // myChart.data.datasets[0].borderWidth = borderWidthValue;
  1191. });
  1192. // =========================
  1193. // 線條顏色-折線圖
  1194. // =========================
  1195. const colorInput = document.getElementById('borderColorInput');
  1196. // 添加事件监听器,当颜色选择发生变化时触发
  1197. colorInput.addEventListener('input', function () {
  1198. // 获取当前选择的颜色值
  1199. chartColor = colorInput.value;
  1200. if (myChart) {
  1201. myChart.destroy();
  1202. }
  1203. console.log(chartColor);
  1204. createChart(chartType, data, labels);
  1205. });
  1206. // =========================
  1207. // 模板樣式
  1208. // =========================
  1209. // JavaScript
  1210. document.addEventListener("DOMContentLoaded", function () {
  1211. // 獲取所有帶有 templateImg class 的圖像元素
  1212. var templateImgs = document.querySelectorAll('.templateImg');
  1213. // 為每個圖像元素添加點擊事件監聽器
  1214. templateImgs.forEach(function (img) {
  1215. img.addEventListener('click', function () {
  1216. // 在點擊時印出圖像元素的 value 屬性值
  1217. console.log(this.getAttribute('value'));
  1218. });
  1219. });
  1220. });
  1221. // =========================
  1222. var bgImgelement = document.getElementById('data_chart_box');
  1223. var chartWhitelement = document.getElementById('data_chartJs_box');
  1224. // 背景樣式
  1225. // =========================
  1226. // JavaScript
  1227. document.addEventListener("DOMContentLoaded", function () {
  1228. // 獲取所有帶有 templateImg class 的圖像元素
  1229. var bgImgs = document.querySelectorAll('.bgImg');
  1230. // 為每個圖像元素添加點擊事件監聽器
  1231. bgImgs.forEach(function (img) {
  1232. img.addEventListener('click', function () {
  1233. // 在點擊時印出圖像元素的 value 屬性值
  1234. console.log(this.getAttribute('value'));
  1235. var bgImgUrl = this.getAttribute('value');
  1236. chart_bgimg_url = 'url(' + bgImgUrl + ')';
  1237. if (bgImgUrl === './img/bg07.webp') {
  1238. chartTitle.style.color = "#ffff";
  1239. } else {
  1240. chartTitle.style.color = "#1c2d6d";
  1241. }
  1242. bgImgelement.style.backgroundImage = chart_bgimg_url;
  1243. // bgImgelement.style.backgroundSize = 'cover'; // 調整背景圖片大小
  1244. // bgImgelement.style.backgroundPosition = 'center'; // 調整背景圖片位置
  1245. // bgImgelement.style.backgroundRepeat = 'no-repeat'; // 調整背景圖片重複
  1246. });
  1247. });
  1248. });
  1249. // =========================
  1250. const radioButtons = document.querySelectorAll('input[name="inlineRadioOptions"]');
  1251. var chartTitle = document.querySelector('.chart_title');
  1252. radioButtons.forEach(button => {
  1253. button.addEventListener('click', function () {
  1254. if (this.value === '0') {
  1255. chartTitle.style.textAlign = 'left';
  1256. } else if (this.value === '1') {
  1257. chartTitle.style.textAlign = 'center';
  1258. } else if (this.value === '2') {
  1259. chartTitle.style.textAlign = 'right';
  1260. }
  1261. });
  1262. });
  1263. // 圖表標題
  1264. var keywordInput = document.getElementById('title_data');
  1265. // 初始时将输入框的值设置为标题的文本内容
  1266. keywordInput.value = chartTitle.textContent;
  1267. // 监听输入框的输入事件,实现内容同步更新
  1268. keywordInput.addEventListener('input', function () {
  1269. chartTitle.textContent = this.value;
  1270. });
  1271. // 標題字體大小
  1272. // ==========================
  1273. var TitlefontSize = document.getElementById('TitlefontSize');
  1274. TitlefontSize.addEventListener('input', function () {
  1275. var TitlefontSizeValue = TitlefontSize.value;
  1276. chartTitle.style.fontSize = TitlefontSizeValue + 'px';;
  1277. });
  1278. // 標題字體顏色
  1279. // ==========================
  1280. let labelDependent = ""; // 圖表左側 Label
  1281. function get_data(input_text_value) {
  1282. // 重新创建取消令牌和源
  1283. cancelToken = axios.CancelToken;
  1284. source = cancelToken.source();
  1285. // inputField.value = "";
  1286. apiLoading();
  1287. var existingTable = chartJsBox.querySelector('table');
  1288. downloadButton.style.display = "none";
  1289. if (chartDiv.childElementCount != 0) {
  1290. data = [];
  1291. labels = [];
  1292. console.log('已存在')
  1293. chartTitle.textContent = "";
  1294. unit_value.textContent = "";
  1295. bgImgelement.style.backgroundImage = "";
  1296. chartWhitelement.style.background = "";
  1297. document.getElementById('img_box_url').src = "";
  1298. }
  1299. if (existingTable) {
  1300. unit_value.textContent = "";
  1301. chartJsBox.removeChild(existingTable);
  1302. chartTitle.textContent = "";
  1303. bgImgelement.style.backgroundImage = "";
  1304. chartWhitelement.style.background = "";
  1305. document.getElementById('img_box_url').src = "";
  1306. }
  1307. if (myChart) {
  1308. unit_value.textContent = "";
  1309. data = [];
  1310. labels = [];
  1311. chartTitle.textContent = "";
  1312. bgImgelement.style.backgroundImage = "";
  1313. chartWhitelement.style.background = "";
  1314. myChart.destroy();
  1315. document.getElementById('img_box_url').src = "";
  1316. }
  1317. const apiUrl = `https://cmm.ai:8080/answer_with_token?token=${accessToken}&question=${input_text_value}`;
  1318. axios.get(apiUrl, {
  1319. cancelToken: source.token // 将取消令牌传递给 Axios 请求
  1320. })
  1321. .then(response => {
  1322. apiHideLoading();
  1323. console.log(response);
  1324. if (response.data.data == "Failed to generate chart!") {
  1325. alert('無法生成圖表')
  1326. return
  1327. } else {
  1328. var chart_info = response.data.chart_info.Title;
  1329. chartType = response.data.chart_info.Chart_type;
  1330. var finance_img_url = response.data.imageUrl_info.imageUrl;
  1331. if (response.data.imageUrl_info != null) {
  1332. console.log(finance_img_url);
  1333. document.getElementById('img_box_url').src = finance_img_url;
  1334. }
  1335. keywordInput.value = chart_info;
  1336. chartTitle.textContent = chart_info;
  1337. unit = response.data.chart_info.Unit_of_dependent_variable;
  1338. var Label_dependent_variable = response.data.chart_info.Label_dependent_variable;
  1339. var Label_independent_variable = response.data.chart_info.Label_independent_variable;
  1340. unit_data.value = response.data.chart_info.Unit_of_dependent_variable;
  1341. if (response.data.chart_info.Unit_of_dependent_variable == "") {
  1342. unit_value.textContent = ""
  1343. } else {
  1344. unit_value.textContent = '單位:' + response.data.chart_info.Unit_of_dependent_variable;
  1345. }
  1346. dataArray = response.data.data;
  1347. if (chartType === "table") {
  1348. unit_value.textContent = "";
  1349. createTable(dataArray, unit, Label_dependent_variable, Label_independent_variable);
  1350. // document.getElementById('img_box_url').src = "";
  1351. } else if (chartType === "bar") {
  1352. console.log('圖表類型bar')
  1353. $('#textToChart').hide();
  1354. extractAndGenerateChart(dataArray);
  1355. } else if (chartType === "doughnut") {
  1356. unit_value.textContent = "";
  1357. $('#textToChart').hide();
  1358. generatePieChart(dataArray)
  1359. } else {
  1360. $('#chartdiv').hide();
  1361. generateChart(dataArray);
  1362. }
  1363. // }
  1364. // 在這裡處理成功獲取 JSON 的情況
  1365. // console.log(response);
  1366. // switch (chartType) {
  1367. // case "line":
  1368. // document.getElementById("inlineRadio4").checked = true; // 折线图
  1369. // break;
  1370. // case "bar":
  1371. // document.getElementById("inlineRadio5").checked = true; // 柱状图
  1372. // labelDependent = response.data.chart_info.Label_dependent_variable;
  1373. // break;
  1374. // case "pie":
  1375. // document.getElementById("inlineRadio6").checked = true; // 圆饼图
  1376. // break;
  1377. // case "table":
  1378. // document.getElementById("inlineRadio7").checked = true; // 表格
  1379. // break;
  1380. // default:
  1381. // // 默认情况
  1382. }
  1383. // generateChart(dataArray);
  1384. })
  1385. .catch(error => {
  1386. // 错误处理逻辑,包括取消请求的情况
  1387. apiHideLoading();
  1388. if (axios.isCancel(error)) {
  1389. console.log('请求被取消:', error.message);
  1390. } else {
  1391. console.error('发生错误:', error);
  1392. }
  1393. });
  1394. }
  1395. // 取消请求的函数
  1396. function cancelRequest() {
  1397. if (source) {
  1398. source.cancel('取消了 API 请求');
  1399. apiHideLoading(); // 隐藏加载状态
  1400. }
  1401. }
  1402. document.getElementById('cancel_send_data').addEventListener('click', cancelRequest);
  1403. $(document).ready(function () {
  1404. $('#dataTable').DataTable();
  1405. });
  1406. function resizeChart() {
  1407. if (myChart) {
  1408. myChart.resize();
  1409. }
  1410. }
  1411. document.addEventListener('visibilitychange', function () {
  1412. console.log('Visibility changed:', document.visibilityState);
  1413. if (document.visibilityState === 'visible') {
  1414. resizeChart();
  1415. }
  1416. });
  1417. // 监听窗口resize事件
  1418. window.addEventListener('resize', resizeChart);
  1419. var setting_button = document.getElementById('chart_seeting_button');
  1420. var closeButton = document.getElementById('style_setting_close');
  1421. var closeButton_table = document.getElementById('style_setting_close_table');
  1422. // var borderSetting = document.getElementById('borderSetting');
  1423. var lineChartSetting = document.getElementById('lineChartSetting');
  1424. var barChartSetting = document.getElementById('barChartSetting');
  1425. var table_setting = document.getElementById('table_setting');
  1426. var chart_category = document.getElementById('chart_category');
  1427. // 获取 chartDiv 元素
  1428. setting_button.addEventListener('click', () => {
  1429. console.log('setting_button')
  1430. if (myChart === null && !chartJsBox.querySelector('table') && chartDiv.childElementCount === 0) {
  1431. alert('請先生成圖表');
  1432. return
  1433. }
  1434. if (chartType === 'table') {
  1435. console.log('表格');
  1436. sidebar.classList.toggle('show');
  1437. sidebar.classList.toggle('hidden');
  1438. barChartSetting.style.display = 'none';
  1439. lineChartSetting.style.display = 'none';
  1440. table_setting.style.display = 'block';
  1441. chart_category.style.display = 'none';
  1442. } else if (chartType === "bar") {
  1443. console.log('柱狀圖')
  1444. sidebar.classList.toggle('show');
  1445. sidebar.classList.toggle('hidden');
  1446. barChartSetting.style.display = 'block';
  1447. lineChartSetting.style.display = 'none';
  1448. table_setting.style.display = 'none';
  1449. } else if (chartType === "doughnut") {
  1450. console.log('快捷功能圓餅圖')
  1451. sidebar.classList.toggle('show');
  1452. sidebar.classList.toggle('hidden');
  1453. barChartSetting.style.display = 'none';
  1454. lineChartSetting.style.display = 'none';
  1455. table_setting.style.display = 'none';
  1456. chart_category.style.display = 'none';
  1457. } else {
  1458. sidebar.classList.toggle('show');
  1459. sidebar.classList.toggle('hidden');
  1460. table_setting.style.display = 'none';
  1461. barChartSetting.style.display = 'none';
  1462. lineChartSetting.style.display = 'block';
  1463. }
  1464. });
  1465. closeButton.addEventListener('click', () => {
  1466. sidebar.classList.remove('show');
  1467. sidebar.classList.add('hidden');
  1468. });
  1469. const finance_options = [
  1470. { name: "原油油桶", imageUrl: "./img/finance/01.webp" },
  1471. { name: "黃金", imageUrl: "./img/finance/02.webp" },
  1472. { name: "房屋", imageUrl: "./img/finance/03.webp" },
  1473. { name: "車", imageUrl: "./img/finance/04.webp" },
  1474. { name: "股市", imageUrl: "./img/finance/05.webp" },
  1475. { name: "晶圓", imageUrl: "./img/finance/06.webp" },
  1476. { name: "TSMC 台積電", imageUrl: "./img/finance/07.webp" },
  1477. { name: "台幣", imageUrl: "./img/finance/08.webp" },
  1478. { name: "美金", imageUrl: "./img/finance/09.webp" },
  1479. { name: "購物車", imageUrl: "./img/finance/10.webp" },
  1480. { name: "美中貿易", imageUrl: "./img/finance/11.webp" },
  1481. { name: "世界地圖", imageUrl: "./img/finance/12.webp" },
  1482. { name: "小麥", imageUrl: "./img/finance/13.webp" },
  1483. { name: "玉米", imageUrl: "./img/finance/14.webp" },
  1484. { name: "智慧產線", imageUrl: "./img/finance/15.webp" }
  1485. ];
  1486. const selectOptions = document.getElementById('finance_options');
  1487. const selectOptions_table = document.getElementById('finance_options_table');
  1488. finance_options.forEach((option, index) => {
  1489. const optionElement = document.createElement('option');
  1490. optionElement.value = index + 1; // 值从 1 开始
  1491. optionElement.textContent = option.name;
  1492. optionElement.setAttribute('data-image-url', option.imageUrl); // 存储图片 URL
  1493. selectOptions.appendChild(optionElement);
  1494. });
  1495. selectOptions.addEventListener('change', function () {
  1496. const selectedOption = selectOptions.options[selectOptions.selectedIndex];
  1497. const imageUrl = selectedOption.getAttribute('data-image-url');
  1498. document.getElementById('img_box_url').src = imageUrl;
  1499. });
  1500. // finance_options.forEach((option, index) => {
  1501. // const optionElement = document.createElement('option');
  1502. // optionElement.value = index + 1; // 值从 1 开始
  1503. // optionElement.textContent = option.name;
  1504. // optionElement.setAttribute('data-image-url', option.imageUrl); // 存储图片 URL
  1505. // selectOptions_table.appendChild(optionElement);
  1506. // });
  1507. // selectOptions_table.addEventListener('change', function () {
  1508. // const selectedOption = selectOptions_table.options[selectOptions_table.selectedIndex];
  1509. // const imageUrl = selectedOption.getAttribute('data-image-url');
  1510. // document.getElementById('img_box_url').src = imageUrl;
  1511. // });
  1512. document.querySelectorAll('input[name="ChartOptions"]').forEach(function (radio) {
  1513. radio.addEventListener('change', function () {
  1514. // 檢查哪個單選按鈕被選中
  1515. if (this.value === 'line') {
  1516. // 如果選擇了折線圖,執行相應的操作
  1517. console.log("選擇了折線圖");
  1518. // 在這裡執行顯示折線圖的相關代碼
  1519. } else if (this.value === 'doughnut') {
  1520. // 如果選擇了柱狀圖,執行相應的操作
  1521. console.log("選擇了柱狀圖");
  1522. // 在這裡執行顯示柱狀圖的相關代碼
  1523. }
  1524. });
  1525. });
  1526. function tokencheck() {
  1527. if (!localStorage.getItem("access_token")) {
  1528. window.location.href = "./login.html";
  1529. } else {
  1530. console.log("存在");
  1531. }
  1532. }
  1533. tokencheck();
  1534. function logout() {
  1535. localStorage.removeItem("access_token");
  1536. }
  1537. $(document).on("click", "#logout", function (event) {
  1538. alert("登出成功");
  1539. logout();
  1540. location.reload();
  1541. });
  1542. // speech_to_text
  1543. var closeRecord = document.getElementById('recording_block_close');
  1544. var recording_block = document.getElementById('recording_block');
  1545. var PageLM = "2024-05-07 18:33";
  1546. var recording_button = document.getElementById('recording_button');
  1547. var stopButton = document.getElementById('stop');
  1548. var audio = document.getElementById('audio');
  1549. var recorder, audioBlob;
  1550. closeRecord.addEventListener('click', () => {
  1551. recPause();
  1552. console.log('record-click')
  1553. recording_block.classList.remove('show');
  1554. recording_block.classList.add('hidden');
  1555. });
  1556. function reclog(s, color) {
  1557. var now = new Date();
  1558. var t = ("0" + now.getHours()).substr(-2)
  1559. + ":" + ("0" + now.getMinutes()).substr(-2)
  1560. + ":" + ("0" + now.getSeconds()).substr(-2);
  1561. var div = document.createElement("div");
  1562. var elem = document.querySelector(".reclog");
  1563. elem.insertBefore(div, elem.firstChild);
  1564. div.innerHTML = '<div style="color:' + (!color ? "" : color == 1 ? "red" : color == 2 ? "#0b1" : color) + '">[' + t + ']' + s + '</div>';
  1565. };
  1566. window.onerror = function (message, url, lineNo, columnNo, error) {
  1567. //https://www.cnblogs.com/xianyulaodi/p/6201829.html
  1568. reclog('<span style="color:red">【Uncaught Error】' + message + '<pre>' + "at:" + lineNo + ":" + columnNo + " url:" + url + "\n" + (error && error.stack || Html_$T("kBaF::不能获得错误堆栈")) + '</pre></span>');
  1569. };
  1570. if (!window.Html_$T) {//没有提供本页面用的国际化多语言支持时 返回中文文本
  1571. window.Html_$T = function () {
  1572. var a = arguments, txt = a[0].replace(/^.+?::/, ""), n = 0;
  1573. for (var i = 0; i < a.length; i++) { if (typeof a[i] == "number") { n = i; break } }
  1574. txt = txt.replace(/\{(\d+)\}/g, function (v, f) { v = a[+f + n]; return v == null ? "" : v });
  1575. return txt;
  1576. }
  1577. window.Html_xT = function (v) { return v }
  1578. }
  1579. if (window.Recorder) {
  1580. // reclog(Html_$T('BL9u::頁面已準備好,請先點擊打開錄音,然後點擊錄製'), 2);
  1581. alert('錄音已準備好,請先點擊右下角打開錄音,即可開始錄製')
  1582. } else {
  1583. reclog(Html_$T("YzPd::js文件加载失败,请刷新重试!"), "#f00;font-size:50px");
  1584. console.log('js文件加载失敗,请刷新重试!')
  1585. }
  1586. recording_button.addEventListener('click', async () => {
  1587. recording_block.classList.toggle('show');
  1588. recording_block.classList.toggle('hidden');
  1589. recStart();
  1590. });
  1591. function recStart() {//打开了录音后才能进行start、stop调用
  1592. rec.start();
  1593. };
  1594. stopButton.addEventListener('click', async () => {
  1595. // recorder.stop();
  1596. // uploadAudio(audioBlob);
  1597. recStop();
  1598. });
  1599. window.onload = function () {
  1600. recOpen();
  1601. };
  1602. // 錄音程式
  1603. // =======================
  1604. var rec, wave, recBlob;
  1605. /**调用open打开录音请求好录音权限 Call open to open the recording and request the recording permission**/
  1606. var recOpen = function () {//一般在显示出录音按钮或相关的录音界面时进行此方法调用,后面用户点击开始录音时就能畅通无阻了
  1607. rec = null;
  1608. wave = null;
  1609. recBlob = null;
  1610. var newRec = Recorder({
  1611. type: "mp3", sampleRate: 16000, bitRate: 16 //mp3格式,指定采样率hz、比特率kbps,其他参数使用默认配置;注意:是数字的参数必须提供数字,不要用字符串;需要使用的type类型,需提前把格式支持文件加载进来,比如使用wav格式需要提前加载wav.js编码引擎
  1612. , onProcess: function (buffers, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx, asyncEnd) {
  1613. // // //录音实时回调,大约1秒调用12次本回调
  1614. document.querySelector(".recpowerx").style.width = powerLevel + "%";
  1615. document.querySelector(".recpowert").innerText = formatMs(bufferDuration, 1) + " / " + powerLevel;
  1616. // //可视化图形绘制
  1617. // wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
  1618. }
  1619. });
  1620. newRec.open(function () {//打开麦克风授权获得相关资源
  1621. rec = newRec;
  1622. //此处创建这些音频可视化图形绘制浏览器支持妥妥的
  1623. // wave = Recorder.FrequencyHistogramView({ elem: ".recwave" });
  1624. reclog(Html_$T("GVCa::已打開錄音,可以點擊錄制開始錄音"), 2);
  1625. }, function (msg, isUserNotAllow) {//用户拒绝未授权或不支持
  1626. reclog((isUserNotAllow ? "UserNotAllow, " : "") + Html_$T("TOOV::打開錄音失敗:") + msg, 1);
  1627. });
  1628. };
  1629. /**关闭录音,释放资源 Close recording, release resources**/
  1630. function recClose() {
  1631. if (rec) {
  1632. rec.close();
  1633. reclog(Html_$T("jqOs::已关闭"));
  1634. } else {
  1635. reclog(Html_$T("VOOw::未打开录音"), 1);
  1636. };
  1637. };
  1638. /**开始录音 Start recording**/
  1639. function recStart() {//打开了录音后才能进行start、stop调用
  1640. if (rec && Recorder.IsOpen()) {
  1641. recBlob = null;
  1642. rec.start();
  1643. reclog(Html_$T("CGdy::已經開始錄音...") + " " + rec.set.type + " " + rec.set.sampleRate + " " + rec.set.bitRate + "kbps");
  1644. } else {
  1645. reclog(Html_$T("ajKR::未打开錄音"), 1);
  1646. };
  1647. };
  1648. /**暂停录音 Passing recording**/
  1649. function recPause() {
  1650. if (rec && Recorder.IsOpen()) {
  1651. rec.pause();
  1652. reclog(Html_$T("GvCy::已暂停"));
  1653. } else {
  1654. reclog(Html_$T("gCAR::未打開錄音"), 1);
  1655. };
  1656. };
  1657. /**恢复录音 Resume recording**/
  1658. function recResume() {
  1659. if (rec && Recorder.IsOpen()) {
  1660. rec.resume();
  1661. reclog(Html_$T("5q1K::继续录音中..."));
  1662. } else {
  1663. reclog(Html_$T("Ob6S::未打开录音"), 1);
  1664. };
  1665. };
  1666. /**结束录音,得到音频文件 Stop recording and get audio files**/
  1667. function recStop() {
  1668. if (!(rec && Recorder.IsOpen())) {
  1669. reclog(Html_$T("5JuL::未打開錄音"), 1);
  1670. return;
  1671. };
  1672. rec.stop(function (blob, duration) {
  1673. console.log(blob, (window.URL || webkitURL).createObjectURL(blob), Html_xT(Html_$T("gOix::時長:{1}ms", 0, duration)));
  1674. recBlob = blob;
  1675. var file = new File([blob], "recording.mp3", { type: "audio/mp3" });
  1676. // console.log(file);
  1677. handleAudioToText(file);
  1678. reclog(Html_$T("0LHf::已錄製mp3:{1}ms {2}字節,可以點擊播放、上傳、本地下载了", 0, formatMs(duration), blob.size), 2);
  1679. }, function (msg) {
  1680. reclog(Html_$T("kGZO::錄音失敗:") + msg, 1);
  1681. });
  1682. };
  1683. /**播放 Play**/
  1684. function recPlay() {
  1685. if (!recBlob) {
  1686. reclog(Html_$T("tIke::请先錄音,然后停止後再播放"), 1);
  1687. return;
  1688. };
  1689. var cls = ("a" + Math.random()).replace(".", "");
  1690. reclog(Html_$T('GlWb::播放中: ') + '<span class="' + cls + '"></span>');
  1691. var audio = document.createElement("audio");
  1692. audio.controls = true;
  1693. document.querySelector("." + cls).appendChild(audio);
  1694. //简单利用URL生成播放地址,注意不用了时需要revokeObjectURL,否则霸占内存
  1695. audio.src = (window.URL || webkitURL).createObjectURL(recBlob);
  1696. audio.play();
  1697. setTimeout(function () {
  1698. (window.URL || webkitURL).revokeObjectURL(audio.src);
  1699. }, 5000);
  1700. };
  1701. /**上传 Upload**/
  1702. function recUpload() {
  1703. var blob = recBlob;
  1704. if (!blob) {
  1705. reclog(Html_$T("DUTn::请先录音,然后停止后再上传"), 1);
  1706. return;
  1707. };
  1708. //本例子假设使用原始XMLHttpRequest请求方式,实际使用中自行调整为自己的请求方式
  1709. //录音结束时拿到了blob文件对象,可以用FileReader读取出内容,或者用FormData上传
  1710. var api = "http://127.0.0.1:9528";
  1711. var onreadystatechange = function (xhr, title) {
  1712. return function () {
  1713. if (xhr.readyState == 4) {
  1714. if (xhr.status == 200) {
  1715. reclog(title + Html_$T("G2MU::上传成功") + ' <span style="color:#999">response: ' + xhr.responseText + '</span>', 2);
  1716. } else {
  1717. reclog(title + Html_$T("TUdi::没有完成上传,演示上传地址无需关注上传结果,只要浏览器控制台内Network面板内看到的请求数据结构是预期的就ok了。"), "#d8c1a0");
  1718. console.error(Html_xT(title + Html_$T("HjDi::上传失败")), xhr.status, xhr.responseText);
  1719. };
  1720. };
  1721. };
  1722. };
  1723. reclog(Html_$T("QnSI::开始上传到{1},请稍候... (你可以先到源码 /assets/node-localServer 目录内执行 npm run start 来运行本地测试服务器)", 0, api));
  1724. /***方式一:将blob文件转成base64纯文本编码,使用普通application/x-www-form-urlencoded表单上传***/
  1725. var reader = new FileReader();
  1726. reader.onloadend = function () {
  1727. var postData = "";
  1728. postData += "mime=" + encodeURIComponent(blob.type);//告诉后端,这个录音是什么格式的,可能前后端都固定的mp3可以不用写
  1729. postData += "&upfile_b64=" + encodeURIComponent((/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result) || [])[1]) //录音文件内容,后端进行base64解码成二进制
  1730. //...其他表单参数
  1731. var xhr = new XMLHttpRequest();
  1732. xhr.open("POST", api + "/uploadBase64");
  1733. xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  1734. xhr.onreadystatechange = onreadystatechange(xhr, Html_$T("gG1f::上传方式一【Base64】"));
  1735. xhr.send(postData);
  1736. };
  1737. reader.readAsDataURL(blob);
  1738. /***方式二:使用FormData用multipart/form-data表单上传文件***/
  1739. var form = new FormData();
  1740. form.append("upfile", blob, "recorder.mp3"); //和普通form表单并无二致,后端接收到upfile参数的文件,文件名为recorder.mp3
  1741. //...其他表单参数
  1742. var xhr = new XMLHttpRequest();
  1743. xhr.open("POST", api + "/upload");
  1744. xhr.onreadystatechange = onreadystatechange(xhr, Html_$T("vDzB::上传方式二【FormData】"));
  1745. xhr.send(form);
  1746. };
  1747. /**本地下载 Local download**/
  1748. function recLocalDown() {
  1749. if (!recBlob) {
  1750. reclog(Html_$T("M86h::请先录音,然后停止后再下载"), 1);
  1751. return;
  1752. };
  1753. var cls = ("a" + Math.random()).replace(".", "");
  1754. recdown64.lastCls = cls;
  1755. reclog(Html_$T('vJPl::点击 ') + '<span class="' + cls + '"></span>' + Html_$T('Whtc:: 下载,或复制文本')
  1756. + '<button onclick="recdown64(\'' + cls + '\')">' + Html_$T('XK4l::生成Base64文本') + '</button><span class="' + cls + '_b64"></span>');
  1757. var fileName = "recorder-" + Date.now() + ".mp3";
  1758. var downA = document.createElement("A");
  1759. downA.innerHTML = Html_$T("g8Fy::下载 ") + fileName;
  1760. downA.href = (window.URL || webkitURL).createObjectURL(recBlob);
  1761. downA.download = fileName;
  1762. document.querySelector("." + cls).appendChild(downA);
  1763. if (/mobile/i.test(navigator.userAgent)) {
  1764. alert(Html_xT(Html_$T("DIEK::因移动端绝大部分国产浏览器未适配Blob Url的下载,所以本demo代码在移动端未调用downA.click()。请尝试点击日志中显示的下载链接下载")));
  1765. } else {
  1766. downA.click();
  1767. }
  1768. //不用了时需要revokeObjectURL,否则霸占内存
  1769. //(window.URL||webkitURL).revokeObjectURL(downA.href);
  1770. };
  1771. function recdown64(cls) {
  1772. var el = document.querySelector("." + cls + "_b64");
  1773. if (recdown64.lastCls != cls) {
  1774. el.innerHTML = '<span style="color:red">' + Html_$T("eKKx::老的数据没有保存,只支持最新的一条") + '</span>';
  1775. return;
  1776. }
  1777. var reader = new FileReader();
  1778. reader.onloadend = function () {
  1779. el.innerHTML = '<textarea></textarea>';
  1780. el.querySelector("textarea").value = reader.result;
  1781. };
  1782. reader.readAsDataURL(recBlob);
  1783. };
  1784. var formatMs = function (ms, all) {
  1785. var ss = ms % 1000; ms = (ms - ss) / 1000;
  1786. var s = ms % 60; ms = (ms - s) / 60;
  1787. var m = ms % 60; ms = (ms - m) / 60;
  1788. var h = ms;
  1789. var t = (h ? h + ":" : "")
  1790. + (all || h + m ? ("0" + m).substr(-2) + ":" : "")
  1791. + (all || h + m + s ? ("0" + s).substr(-2) + "″" : "")
  1792. + ("00" + ss).substr(-3);
  1793. return t;
  1794. };
  1795. // =======================
  1796. let mediaRecorder;
  1797. let audioChunks = [];
  1798. var recordText = document.getElementById('record_text');
  1799. async function handleAudioToText(file) {
  1800. console.log('tts')
  1801. let audioLang = "cmn-Hant-TW"; // 音訊語言
  1802. // let lang = localStorage.getItem("lang");
  1803. let lang = "zh-tw";
  1804. console.log("lang", lang);
  1805. switch (lang) {
  1806. case "zh-tw":
  1807. audioLang = "cmn-Hant-TW";
  1808. break;
  1809. case "en-us":
  1810. audioLang = "en-US";
  1811. break;
  1812. case "ja-jp":
  1813. audioLang = "ja-JP";
  1814. break;
  1815. case "ko-kr":
  1816. audioLang = "ko-KR";
  1817. break;
  1818. default:
  1819. break;
  1820. }
  1821. let url = `https://cmm.ai:9001/gcp/speech-to-text?language_code=${audioLang}`;
  1822. const formData = new FormData();
  1823. formData.append("file", file);
  1824. try {
  1825. const response = await axios.post(url, formData);
  1826. console.log("response", response);
  1827. input_text_value = response.data;
  1828. recording_block.classList.remove('show');
  1829. recording_block.classList.add('hidden');
  1830. if (input_text_value.length === 0) {
  1831. alert('請再說一次')
  1832. return
  1833. }
  1834. console.log(input_text_value[0]);
  1835. inputField.value = input_text_value[0];
  1836. // Show the result for a short time before executing get_data
  1837. get_data(input_text_value);
  1838. // get_data(input_text_value);
  1839. } catch (error) {
  1840. console.log("error", error);
  1841. }
  1842. }