text-to-chart.js 77 KB

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