如何加载本地JSON文件制作条形竞速图?
加载本地JSON文件实现条形竞速图的解决方案
问题描述
我使用一段代码加载在线JSON文件时可正常生成条形竞速图,但无法加载本地JSON文件。我认为需要修改以下代码段:
dataset = await fetch( 'https://demo-live-data.highcharts.com/population.json' ).then(response => response.json());
完整代码如下:
const startYear = 1960, endYear = 2018, btn = document.getElementById('play-pause-button'), input = document.getElementById('play-range'), nbr = 20; let dataset, chart; /* * Animate dataLabels functionality */ (function (H) { const FLOAT = /^-?\d+\.?\d*$/; // Add animated textSetter, just like fill/strokeSetters H.Fx.prototype.textSetter = function () { let startValue = this.start.replace(/ /g, ''), endValue = this.end.replace(/ /g, ''), currentValue = this.end.replace(/ /g, ''); if ((startValue || '').match(FLOAT)) { startValue = parseInt(startValue, 10); endValue = parseInt(endValue, 10); // No support for float currentValue = Highcharts.numberFormat( Math.round(startValue + (endValue - startValue) * this.pos), 0 ); } this.elem.endText = this.end; this.elem.attr(this.prop, currentValue, null, true); }; // Add textGetter, not supported at all at this moment: H.SVGElement.prototype.textGetter = function () { const ct = this.text.element.textContent || ''; return this.endText ? this.endText : ct.substring(0, ct.length / 2); }; // Temporary change label.attr() with label.animate(): // In core it's simple change attr(...) => animate(...) for text prop H.wrap(H.Series.prototype, 'drawDataLabels', function (proceed) { const attr = H.SVGElement.prototype.attr, chart = this.chart; if (chart.sequenceTimer) { this.points.forEach(point => (point.dataLabels || []).forEach( label => (label.attr = function (hash) { if (hash && hash.text !== undefined) { const text = hash.text; delete hash.text; return this .attr(hash) .animate({ text }); } return attr.apply(this, arguments); }) ) ); } const ret = proceed.apply( this, Array.prototype.slice.call(arguments, 1) ); this.points.forEach(p => (p.dataLabels || []).forEach(d => (d.attr = attr)) ); return ret; }); }(Highcharts)); function getData(year) { const output = Object.entries(dataset) .map(country => { const [countryName, countryData] = country; return [countryName, Number(countryData[year])]; }) .sort((a, b) => b[1] - a[1]); return [output[0], output.slice(1, nbr)]; } function getSubtitle() { const population = (getData(input.value)[0][1] / 1000000000).toFixed(2); return `<span style="font-size: 80px">${input.value}</span> <br> <span style="font-size: 22px"> Total: <b>: ${population}</b> billion </span>`; } (async () => { dataset = await fetch( 'https://demo-live-data.highcharts.com/population.json' ).then(response => response.json()); chart = Highcharts.chart('container', { chart: { animation: { duration: 500 }, marginRight: 50 }, title: { text: 'World population by country', align: 'left' }, subtitle: { useHTML: true, text: getSubtitle(), floating: true, align: 'right', verticalAlign: 'middle', y: -20, x: -100 }, legend: { enabled: false }, xAxis: { type: 'category' }, yAxis: { opposite: true, tickPixelInterval: 150, title: { text: null } }, plotOptions: { series: { animation: false, groupPadding: 0, pointPadding: 0.1, borderWidth: 0, colorByPoint: true, dataSorting: { enabled: true, matchByName: true }, type: 'bar', dataLabels: { enabled: true } } }, series: [ { type: 'bar', name: startYear, data: getData(startYear)[1] } ], responsive: { rules: [{ condition: { maxWidth: 550 }, chartOptions: { xAxis: { visible: false }, subtitle: { x: 0 }, plotOptions: { series: { dataLabels: [{ enabled: true, y: 8 }, { enabled: true, format: '{point.name}', y: -8, style: { fontWeight: 'normal', opacity: 0.7 } }] } } } }] } }); })(); /* * Pause the timeline, either when the range is ended, or when clicking the pause button. * Pausing stops the timer and resets the button to play mode. */ function pause(button) { button.title = 'play'; button.className = 'fa fa-play'; clearTimeout(chart.sequenceTimer); chart.sequenceTimer = undefined; } /* * Update the chart. This happens either on updating (moving) the range input, * or from a timer when the timeline is playing. */ function update(increment) { if (increment) { input.value = parseInt(input.value, 10) + increment; } if (input.value >= endYear) { // Auto-pause pause(btn); } chart.update( { subtitle: { text: getSubtitle() } }, false, false, false ); chart.series[0].update({ name: input.value, data: getData(input.value)[1] }); } /* * Play the timeline. */ function play(button) { button.title = 'pause'; button.className = 'fa fa-pause'; chart.sequenceTimer = setInterval(function () { update(1); }, 500); } btn.addEventListener('click', function () { if (chart.sequenceTimer) { pause(this); } else { play(this); } }); /* * Trigger the update on the range bar click. */ input.addEventListener('click', function () { update(); });
解决方案
1. 本地HTTP服务器加载(推荐)
直接用file://协议打开本地文件时,浏览器的同源策略会阻止fetch请求本地资源,因此最可靠的方式是通过本地HTTP服务器访问:
- 将HTML文件和本地JSON文件(比如命名为
population.json)放在同一目录下 - 修改fetch代码为:
dataset = await fetch('./population.json') .then(response => { if (!response.ok) { throw new Error('本地JSON文件加载失败'); } return response.json(); }) .catch(error => console.error('加载错误:', error));
- 启动本地服务器:比如使用Python内置服务器,在目录下执行命令
python -m http.server 8000,然后在浏览器访问http://localhost:8000/你的HTML文件名.html
2. 将JSON直接嵌入代码(适合小数据量)
如果JSON文件体积不大,可以直接把内容复制到代码中,省去网络请求步骤:
// 替换原来的fetch异步代码,直接赋值JSON内容 const dataset = { // 粘贴你的population.json文件内容 };
3. 调整浏览器安全设置(临时测试用,不推荐)
仅用于本地临时测试,不建议日常使用:
- 对于Chrome浏览器,右键快捷方式,在"目标"字段末尾添加
--allow-file-access-from-files(注意前面有空格) - 启动浏览器后打开本地HTML文件,即可绕过同源限制加载本地JSON
内容的提问来源于stack exchange,提问作者user21474411
相关产品推荐
相关产品推荐

