Last active
September 18, 2020 16:11
-
-
Save hankei6km/92cc2b4ef0805cf8201e06a5af6c7cb9 to your computer and use it in GitHub Desktop.
Hugo の shortcode でプロッター表示 https://hankei6km.github.io/posts/2020-08-hugo_shortcode_plotter/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!-- | |
| button icons | |
| https://material.io/resources/icons/ Apache license version 2.0. | |
| --> | |
| {{- /* unique_id https://discourse.gohugo.io/t/generating-a-unique-number-or-string/7930 */ -}} | |
| {{- $unique_id := md5 now.UnixNano -}} | |
| {{- $data := index (index $.Site.Data.plotter.a1 (.Get "data")) "series" -}} | |
| {{- $series := "" -}} | |
| {{- $xaxis_series := "" -}} | |
| {{- $point_annotaion_series := "" -}} | |
| {{- $window_size := "200" -}} | |
| {{- with .Parent -}} | |
| {{- $series = .Get "series" -}} | |
| {{- $xaxis_series = .Get "xaxis_series" -}} | |
| {{- $point_annotaion_series = .Get "point_annotaion_series" -}} | |
| {{- if .Get "window_size" -}} | |
| {{- $window_size = .Get "window_size" -}} | |
| {{- end -}} | |
| {{- else -}} | |
| {{- $series = .Get "series" -}} | |
| {{- $xaxis_series = .Get "xaxis_series" -}} | |
| {{- $point_annotaion_series = .Get "point_annotaion_series" -}} | |
| {{- if .Get "window_size" -}} | |
| {{- $window_size = .Get "window_size" -}} | |
| {{- end -}} | |
| {{- end -}} | |
| <div style=' width: 100%; padding-top: 20px; padding-bottom: 40px;'> | |
| <div id='chart{{$unique_id}}' style='width:100%; height: 200'> </div> | |
| <div style='display:flex; flex-direction: row; padding-left: 30px; padding-right: 0px;'> | |
| <div style='flex: initial; display:flex; flex-direction: row;'> | |
| <div style='flex:auto;'> | |
| <button id='playback{{$unique_id}}' style='display: none; border: none;'> | |
| <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='#606060' width='32px' | |
| height='32px'> | |
| <path d='M0 0h24v24H0z' fill='none' /> | |
| <path | |
| d='M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z' /> | |
| </svg> | |
| </button> | |
| </div> | |
| <div style='flex:auto;'> | |
| <button id='pause{{$unique_id}}' style='display: none; border: none;'> | |
| <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='#606060' width='32px' | |
| height='32px'> | |
| <path d='M0 0h24v24H0z' fill='none' /> | |
| <path | |
| d='M9 16h2V8H9v8zm3-14C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm1-4h2V8h-2v8z' /> | |
| </svg> | |
| </button> | |
| </div> | |
| <div style='flex:auto;'> | |
| <button id='replay{{$unique_id}}' style='border: none;'> | |
| <svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" | |
| fill="#606060" width="32px" height="32px"> | |
| <g> | |
| <rect fill="none" height="24" width="24" /> | |
| </g> | |
| <g> | |
| <g> | |
| <path | |
| d="M12,5V1L7,6l5,5V7c3.31,0,6,2.69,6,6s-2.69,6-6,6s-6-2.69-6-6H4c0,4.42,3.58,8,8,8s8-3.58,8-8S16.42,5,12,5z" /> | |
| <g> | |
| <path | |
| d="M9.56,13.49h0.45c0.21,0,0.37-0.05,0.48-0.16s0.16-0.25,0.16-0.43c0-0.08-0.01-0.15-0.04-0.22s-0.06-0.12-0.11-0.17 s-0.11-0.09-0.18-0.11s-0.16-0.04-0.25-0.04c-0.08,0-0.15,0.01-0.22,0.03s-0.13,0.05-0.18,0.1s-0.09,0.09-0.12,0.15 s-0.05,0.13-0.05,0.2H8.65c0-0.18,0.04-0.34,0.11-0.48s0.17-0.27,0.3-0.37s0.27-0.18,0.44-0.23s0.35-0.08,0.54-0.08 c0.21,0,0.41,0.03,0.59,0.08s0.33,0.13,0.46,0.23s0.23,0.23,0.3,0.38s0.11,0.33,0.11,0.53c0,0.09-0.01,0.18-0.04,0.27 s-0.07,0.17-0.13,0.25s-0.12,0.15-0.2,0.22s-0.17,0.12-0.28,0.17c0.24,0.09,0.42,0.21,0.54,0.39s0.18,0.38,0.18,0.61 c0,0.2-0.04,0.38-0.12,0.53s-0.18,0.29-0.32,0.39s-0.29,0.19-0.48,0.24s-0.38,0.08-0.6,0.08c-0.18,0-0.36-0.02-0.53-0.07 s-0.33-0.12-0.46-0.23s-0.25-0.23-0.33-0.38s-0.12-0.34-0.12-0.55h0.85c0,0.08,0.02,0.15,0.05,0.22s0.07,0.12,0.13,0.17 s0.12,0.09,0.2,0.11s0.16,0.04,0.25,0.04c0.1,0,0.19-0.01,0.27-0.04s0.15-0.07,0.2-0.12s0.1-0.11,0.13-0.18s0.04-0.15,0.04-0.24 c0-0.11-0.02-0.21-0.05-0.29s-0.08-0.15-0.14-0.2s-0.13-0.09-0.22-0.11s-0.18-0.04-0.29-0.04H9.56V13.49z" /> | |
| <path | |
| d="M15.3,14.24c0,0.32-0.03,0.6-0.1,0.82s-0.17,0.42-0.29,0.57s-0.28,0.26-0.45,0.33s-0.37,0.1-0.59,0.1 s-0.41-0.03-0.59-0.1s-0.33-0.18-0.46-0.33s-0.23-0.34-0.3-0.57s-0.11-0.5-0.11-0.82V13.5c0-0.32,0.03-0.6,0.1-0.82 s0.17-0.42,0.29-0.57s0.28-0.26,0.45-0.33s0.37-0.1,0.59-0.1s0.41,0.03,0.59,0.1s0.33,0.18,0.46,0.33s0.23,0.34,0.3,0.57 s0.11,0.5,0.11,0.82V14.24z M14.45,13.38c0-0.19-0.01-0.35-0.04-0.48c-0.03-0.13-0.07-0.23-0.12-0.31s-0.11-0.14-0.19-0.17 s-0.16-0.05-0.25-0.05s-0.18,0.02-0.25,0.05s-0.14,0.09-0.19,0.17s-0.09,0.18-0.12,0.31s-0.04,0.29-0.04,0.48v0.97 c0,0.19,0.01,0.35,0.04,0.48s0.07,0.24,0.12,0.32s0.11,0.14,0.19,0.17s0.16,0.05,0.25,0.05s0.18-0.02,0.25-0.05 s0.14-0.09,0.19-0.17s0.09-0.19,0.11-0.32c0.03-0.13,0.04-0.29,0.04-0.48V13.38z" /> | |
| </g> | |
| </g> | |
| </g> | |
| </svg> | |
| </button> | |
| </div> | |
| <div style='flex:auto;'> | |
| <button id='forward{{$unique_id}}' style='border: none;'> | |
| <svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" | |
| fill="#606060" width="32px" height="32px"> | |
| <g> | |
| <rect fill="none" height="24" width="24" /> | |
| </g> | |
| <g> | |
| <g> | |
| <path | |
| d="M18,13c0,3.31-2.69,6-6,6s-6-2.69-6-6s2.69-6,6-6v4l5-5l-5-5v4c-4.42,0-8,3.58-8,8c0,4.42,3.58,8,8,8s8-3.58,8-8H18z" /> | |
| <path | |
| d="M10.06,15.38c-0.29,0-0.62-0.17-0.62-0.54H8.59c0,0.97,0.9,1.23,1.45,1.23c0.87,0,1.51-0.46,1.51-1.25 c0-0.66-0.45-0.9-0.71-1c0.11-0.05,0.65-0.32,0.65-0.92c0-0.21-0.05-1.22-1.44-1.22c-0.62,0-1.4,0.35-1.4,1.16h0.85 c0-0.34,0.31-0.48,0.57-0.48c0.59,0,0.58,0.5,0.58,0.54c0,0.52-0.41,0.59-0.63,0.59H9.56v0.66h0.45c0.65,0,0.7,0.42,0.7,0.64 C10.71,15.11,10.5,15.38,10.06,15.38z" /> | |
| <path | |
| d="M13.85,11.68c-0.14,0-1.44-0.08-1.44,1.82v0.74c0,1.9,1.31,1.82,1.44,1.82c0.14,0,1.44,0.09,1.44-1.82V13.5 C15.3,11.59,13.99,11.68,13.85,11.68z M14.45,14.35c0,0.77-0.21,1.03-0.59,1.03c-0.38,0-0.6-0.26-0.6-1.03v-0.97 c0-0.75,0.22-1.01,0.59-1.01c0.38,0,0.6,0.26,0.6,1.01V14.35z" /> | |
| </g> | |
| </g> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| <input type="range" id="pos{{$unique_id}}" min="0" max="1" style="flex:auto; display:block;"> | |
| </div> | |
| </div> | |
| {{- with .Parent -}} | |
| {{- else -}} | |
| <script src="https://cdn.jsdelivr.net/npm/apexcharts@3.20.0"></script> | |
| {{- end -}} | |
| <script type="text/javascript"> | |
| (function () { | |
| "use strict" | |
| const palette = [ // palette1,palette2 https://apexcharts.com/docs/options/theme/ | |
| '#008FFB', '#00E396', '#FEB019', '#FF4560', '#775DD0', | |
| '#3F51B5', '#03A9F4', '#4CAF50', '#F9CE1D', '#FF9800', | |
| ]; | |
| const uniqueId = '{{$unique_id}}'; | |
| const titleText = '{{.Get "title"}}' ? '{{.Get "title"}}' : ''; | |
| const enabledPalyback = '{{.Get "playback"}}' ? true : false; | |
| const serriesParams = JSON.parse('{{$series}}'); | |
| const rawSeries = JSON.parse('{{ jsonify $data }}') | |
| const xaxisSeriesName = '{{$xaxis_series}}' ? '{{$xaxis_series}}' : ''; | |
| const pointAnnotationSeriesName = '{{$point_annotaion_series}}' ? '{{$point_annotaion_series}}' : ''; | |
| let valWindowSize = parseInt('{{ $window_size }}', 10); | |
| let valLen = 0; | |
| const fullSeries = serriesParams.filter(v => rawSeries.findIndex(r => r.name === v.name) >= 0).map(v => { | |
| const si = rawSeries.find(r => r.name === v.name); | |
| if (valLen < si.data.length) { | |
| valLen = si.data.length; | |
| } | |
| return { | |
| name: v.name, | |
| type: v.type, | |
| showYaxis: v.showYaxis, | |
| color: v.color, | |
| hidden: v.hidden, | |
| data: si.data, | |
| } | |
| }) | |
| if (valWindowSize <= 0) { | |
| valWindowSize = valLen - 1; | |
| } | |
| const fullPointSeries = rawSeries.find(r => r.name === pointAnnotationSeriesName); | |
| const pointSeries = fullPointSeries === undefined ? [] : fullPointSeries.data.map((v, i) => ({ | |
| x: i, | |
| y: v, | |
| marker: { size: 2 }, | |
| borderColor: '#775DD0', | |
| label: { | |
| style: { | |
| color: '#808080', | |
| }, | |
| text: fullPointSeries.name, | |
| } | |
| })).filter(v => v.y !== ''); | |
| const fullTimeSeries = rawSeries.find(r => r.name === xaxisSeriesName); | |
| const timeSeries = fullTimeSeries === undefined ? [] : fullTimeSeries.data.map((v, i) => v); | |
| const intervals = () => { | |
| const m = rawSeries.filter(v => v.name === 'millis'); | |
| if (m.length > 0) { | |
| const ret = new Array(valLen); | |
| for (let i = 0; i + 1 < valLen; i++) { | |
| ret[i] = (m[0].data[i + 1] - m[0].data[i]) * 10; | |
| } | |
| return ret; | |
| } | |
| return new Array(valLen).fill(100); | |
| } | |
| // const getNewSeries = pos => | |
| // fullSeries.map(v => ({ name: v.name, type: v.type, data: v.data.slice(pos, pos + valWindowSize) })); | |
| const playbackBtn = document.querySelector('#playback' + uniqueId); | |
| const pauseBtn = document.querySelector('#pause' + uniqueId); | |
| const replayBtn = document.querySelector('#replay' + uniqueId); | |
| const forwardBtn = document.querySelector('#forward' + uniqueId); | |
| const { setPlayback, isPlayback } = (() => { | |
| if (enabledPalyback) { | |
| let isPlayback = false; | |
| return { | |
| setPlayback: (p) => { | |
| isPlayback = p; | |
| if (p) { | |
| playbackBtn.style.display = 'none'; | |
| pauseBtn.style.display = ''; | |
| } else { | |
| playbackBtn.style.display = ''; | |
| pauseBtn.style.display = 'none'; | |
| } | |
| }, | |
| isPlayback: () => isPlayback, | |
| } | |
| } | |
| return { | |
| setPlayback: (p) => { }, | |
| isPlayback: () => false, | |
| } | |
| })(); | |
| setPlayback(false); | |
| const handlePlaybackClick = (event) => { | |
| setPlayback(true); | |
| updateSeries(curSliderPos()); | |
| } | |
| const handlePauseClick = (event) => { | |
| setPlayback(false); | |
| } | |
| const handleReplayClick = (event) => { | |
| const pos = curSliderPos(); | |
| if (pos - 30 > 0) { | |
| setSliderPos(pos - 30); | |
| updateSeries(curSliderPos()); | |
| } | |
| } | |
| const handleForwardClick = (event) => { | |
| const pos = curSliderPos(); | |
| if (pos + 30 < valLen) { | |
| setSliderPos(pos + 30); | |
| } else { | |
| setSliderPos(valLen - 1); | |
| } | |
| updateSeries(curSliderPos()); | |
| } | |
| playbackBtn.addEventListener('click', handlePlaybackClick); | |
| pauseBtn.addEventListener('click', handlePauseClick); | |
| replayBtn.addEventListener('click', handleReplayClick); | |
| forwardBtn.addEventListener('click', handleForwardClick); | |
| const posSlider = document.querySelector('#pos' + uniqueId); | |
| const curSliderPos = () => parseInt(posSlider.value, 10) + 1; | |
| const setSliderPos = (pos) => { | |
| const value = (pos === undefined) ? 1 : pos; | |
| if (value - 1 !== curSliderPos()) { | |
| posSlider.value = value - 1; | |
| } | |
| } | |
| const handleChange = (event) => { | |
| updateSeries(parseInt(event.target.value, 10)); | |
| } | |
| posSlider.setAttribute('max', valLen); | |
| posSlider.addEventListener('change', handleChange); | |
| let timerId = 0; | |
| const updateSeries = (pos) => { | |
| setSliderPos(pos); | |
| chart.zoomX(pos, pos + valWindowSize); | |
| if (isPlayback()) { | |
| if (timerId !== 0) { | |
| clearTimeout(timerId); | |
| } | |
| if (pos < valLen - valWindowSize) { | |
| timerId = setTimeout(() => updateSeries(pos + 1), intervals[pos]); | |
| } else { | |
| setPlayback(true); | |
| } | |
| } | |
| } | |
| const options = { | |
| chart: { | |
| type: 'line', | |
| animations: { | |
| enabled: false, | |
| }, | |
| events: { | |
| zoomed: (chartContext, { xaxis, yaxis }) => { | |
| if (xaxis.min !== undefined && xaxis.max !== undefined) { | |
| valWindowSize = xaxis.max - xaxis.min; | |
| } else { | |
| valWindowSize = valLen - 1; | |
| } | |
| setSliderPos(xaxis.min); | |
| }, | |
| scrolled: (chartContext, { xaxis }) => { | |
| setSliderPos(xaxis.min); | |
| }, | |
| }, | |
| }, | |
| series: fullSeries.map(v => ({ name: v.name, type: v.type, data: v.data })), | |
| colors: fullSeries.map((v, i) => v.color ? v.color : palette[i]), | |
| yaxis: fullSeries.map((v, i) => { | |
| const opposite = v.opposite === undefined ? false : v.opposite; | |
| const show = v.showYaxis === undefined ? false : v.showYaxis; | |
| return { | |
| opposite: opposite, | |
| show: show, | |
| min: min => { | |
| const pos = curSliderPos() - 1; | |
| // for の方が速いかな。 | |
| // max と共通化できる。 | |
| const r = v.data.slice(pos, pos + valWindowSize).reduce((a, b) => { | |
| return Math.min(a, b); | |
| }) | |
| return r - 10; | |
| }, | |
| max: max => { | |
| const pos = curSliderPos() - 1; | |
| const r = v.data.slice(pos, pos + valWindowSize).reduce((a, b) => { | |
| return Math.max(a, b); | |
| }) | |
| return r + 10; | |
| }, | |
| } | |
| }), | |
| xaxis: { | |
| // type: 'categories', | |
| categories: timeSeries, | |
| labels: { | |
| show: false, | |
| }, | |
| }, | |
| annotations: { | |
| points: pointSeries, | |
| }, | |
| title: { | |
| text: titleText, | |
| }, | |
| } | |
| const chart = new ApexCharts(document.querySelector('#chart' + uniqueId), options); | |
| chart.render(); | |
| fullSeries.forEach((v, i) => { | |
| if (v.hidden) { | |
| chart.hideSeries(v.name); | |
| } | |
| }); | |
| updateSeries(1); | |
| })() | |
| </script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script src='https://cdn.jsdelivr.net/npm/apexcharts'></script> | |
| {{.Inner}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ## テスト表示 | |
| {{< replayPlotterOuter | |
| series="[{\"name\": \"val\", \"showYaxis\":true, \"color\": \"#FF4560\"}, {\"name\": \"gyroX\", \"hidden\": true, \"opposite\": true, \"color\": \"#775DD0\"}, {\"name\": \"gyroY\", \"hidden\": true, \"opposite\": true, \"color\": \"#00E396\"}, {\"name\": \"gyroZ\", \"opposite\": true, \"color\": \"#008FFB\"}]" | |
| xaxis_series="millis" | |
| point_annotaion_series="peakP" | |
| >}} | |
| {{< replayPlotter_a1 | |
| data="2020_09_02_walk_grip_backhand_ble" | |
| title="2020-09-02 ウォーク、日中、曇り、ケース逆手持ち BLE" | |
| >}} | |
| {{< replayPlotter_a1 | |
| data="2020_09_02_walk_fit_under_fingertips_ble" | |
| title="2020-09-02 ウォーク、日中、曇り、ケース指先に沿わせる BLE" | |
| >}} | |
| {{< /replayPlotterOuter >}} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment