[READ-ONLY] Mirror of https://github.com/probablykasper/starchart. GitHub star history graph starchart.kasper.space
chart github github-api github-star graph history star star-history stargazers stars
0

Configure Feed

Select the types of activity you want to include in your feed.

Add tooltip

Kasper (Mar 1, 2023, 2:15 PM +0100) 8eb59281 9924b2ab

+177 -119
+10 -5
src/routes/+page.svelte
··· 4 4 import { onMount } from 'svelte' 5 5 import '../app.sass' 6 6 import Nav from './Nav.svelte' 7 - import LabeledChart from './LabeledChart.svelte' 7 + import ChartComponent from './Chart.svelte' 8 8 import { getNextColorIndex, newChart, type Chart } from './chart' 9 9 import type { UTCTimestamp } from 'lightweight-charts' 10 + import Tooltip from './Tooltip.svelte' 10 11 11 12 let [owner, repo] = ['', ''] 12 13 ··· 116 117 </div> 117 118 {/each} 118 119 120 + {#if $chart?.lines.length > 0} 121 + <ChartComponent {chart} /> 122 + {/if} 119 123 <div class="chart" bind:clientWidth={width}> 120 - {#if $chart && $chart.lines.length > 0} 121 - <LabeledChart {chart} /> 124 + <div bind:this={container} class:hidden={!$chart || $chart.lines.length === 0} /> 125 + {#if $chart?.lines.length > 0} 126 + <Tooltip {chart} /> 122 127 {/if} 123 - <div bind:this={container} class:hidden={!$chart || $chart.lines.length === 0} /> 124 128 </div> 125 129 126 130 <style lang="sass"> ··· 151 155 opacity: 0.7 152 156 .chart 153 157 width: 100% 154 - padding-bottom: 50px 158 + margin-bottom: 50px 159 + position: relative // for tooltip 155 160 .hidden 156 161 display: none 157 162 </style>
+19 -82
src/routes/Chart.svelte
··· 1 1 <script lang="ts"> 2 - import type { UTCTimestamp } from 'lightweight-charts' 3 - import { ColorType } from 'lightweight-charts' 4 2 import type { Chart } from './chart' 3 + import Label from './Label.svelte' 4 + import { ColorType } from 'lightweight-charts' 5 5 6 6 export let chart: Chart 7 7 ··· 47 47 dateFormat: 'yyyy MMM dd', 48 48 }, 49 49 }) 50 + </script> 50 51 51 - let toolTipValues: { name: string; value: number }[] = [] 52 - let toolTipDateStr = 0 53 - let toolTipLeft = 0 54 - let toolTipTop = 0 55 - let tooltipVisible = false 56 - 57 - // update tooltip 58 - $chart.instance.subscribeCrosshairMove((param) => { 59 - if ( 60 - param.point === undefined || 61 - !param.time || 62 - param.point.x < 0 || 63 - param.point.x > $chart.container.clientWidth || 64 - param.point.y < 0 || 65 - param.point.y > $chart.container.clientHeight 66 - ) { 67 - tooltipVisible = false 68 - } else { 69 - // time will be in the same format that we supplied to setData. 70 - // thus it will be YYYY-MM-DD 71 - toolTipDateStr = param.time as UTCTimestamp 72 - tooltipVisible = true 73 - for (let i = 0; i < $chart.lines.length; i++) { 74 - toolTipValues = [] 75 - const lineData = param.seriesData.get($chart.lines[i].instance) 76 - let v = 0 77 - if (lineData && 'value' in lineData && lineData.value !== undefined) { 78 - v = lineData.value 79 - } else if (lineData && 'close' in lineData && lineData.close !== undefined) { 80 - v = lineData.close 81 - } else { 82 - return 83 - } 84 - toolTipValues.push({ 85 - name: $chart.lines[i].name, 86 - value: v, 87 - }) 88 - } 89 - } 90 - }) 91 - </script> 52 + <div class="labels"> 53 + {#each $chart.lines as line (line.name)} 54 + <Label 55 + {line} 56 + onDelete={() => { 57 + chart.deleteLine(line) 58 + chart.save() 59 + }} 60 + onVisibleChange={(visible) => { 61 + line.instance.applyOptions({ visible }) 62 + }} 63 + /> 64 + {/each} 65 + </div> 92 66 93 67 <svelte:window on:resize={chart.resetZoom} /> 94 68 95 - <div class="tooltip" class:hide={!tooltipVisible} style:left={toolTipLeft} style:top={toolTipTop}> 96 - <div style="color: white"> 97 - {toolTipDateStr} 98 - </div> 99 - <table> 100 - <tbody> 101 - <tr> 102 - {#each toolTipValues as toolTipValue} 103 - <td style:color="#ffdd00">{toolTipValue.name}</td> 104 - <td style="font-size: 24px; margin: 4px 0px; color: white"> 105 - {toolTipValue} 106 - </td> 107 - {/each} 108 - </tr> 109 - </tbody> 110 - </table> 111 - </div> 112 - 113 69 <style lang="sass"> 114 - .tooltip 115 - height: 80px 116 - position: absolute 117 - display: none 118 - padding: 8px 119 - box-sizing: border-box 120 - font-size: 12px 121 - text-align: left 122 - z-index: 1000 123 - top: 12px 124 - left: 12px 125 - pointer-events: none 126 - border: 1px solid 127 - border-radius: 2px 128 - font-family: -apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif 129 - webkit-font-smoothing: antialiased 130 - moz-osx-font-smoothing: grayscale 131 - color: white 132 - border-color: #cbcaf7 133 - .hide 134 - display: none 70 + .labels 71 + text-align: center 135 72 </style>
-29
src/routes/LabeledChart.svelte
··· 1 - <script lang="ts"> 2 - import ChartComponent from './Chart.svelte' 3 - import type { Chart } from './chart' 4 - import Label from './Label.svelte' 5 - 6 - export let chart: Chart 7 - </script> 8 - 9 - <div class="labels"> 10 - {#each $chart.lines as line (line.name)} 11 - <Label 12 - {line} 13 - onDelete={() => { 14 - chart.deleteLine(line) 15 - chart.save() 16 - }} 17 - onVisibleChange={(visible) => { 18 - line.instance.applyOptions({ visible }) 19 - }} 20 - /> 21 - {/each} 22 - </div> 23 - 24 - <ChartComponent {chart} /> 25 - 26 - <style lang="sass"> 27 - .labels 28 - text-align: center 29 - </style>
+147
src/routes/Tooltip.svelte
··· 1 + <script lang="ts"> 2 + import type { MouseEventParams, UTCTimestamp } from 'lightweight-charts' 3 + import { onDestroy } from 'svelte' 4 + import type { Chart, DataPoint, Line } from './chart' 5 + import { hexColors } from './color' 6 + 7 + export let chart: Chart 8 + 9 + const margin = 12 10 + let values: { line: Line; value: number }[] = [] 11 + let width: number 12 + let height: number 13 + let left: number 14 + let top: number 15 + 16 + function onMouseMove(e: MouseEvent) { 17 + const bounds = $chart.container.getBoundingClientRect() 18 + const x = e.clientX - bounds.left 19 + const y = e.clientY - bounds.top 20 + // console.log(bounds.left) 21 + // console.log(e.clientX - bounds.left, e.clientY - bounds.top) 22 + left = x + margin 23 + if (left > $chart.container.clientWidth - width) { 24 + left = x - width - margin 25 + } 26 + 27 + top = y + margin 28 + if (top > $chart.container.clientHeight - height) { 29 + top = y - height - margin 30 + } 31 + } 32 + $chart.container.addEventListener('mousemove', onMouseMove) 33 + onDestroy(() => { 34 + $chart.container.removeEventListener('mousemove', onMouseMove) 35 + }) 36 + 37 + function findHighestDataPoint(arr: DataPoint[], max: UTCTimestamp) { 38 + let start = 0 39 + let end = arr.length - 1 40 + let result: DataPoint | null = null 41 + 42 + while (start <= end) { 43 + let middle = Math.floor((start + end) / 2) 44 + 45 + if (arr[middle].t <= max) { 46 + result = arr[middle] 47 + start = middle + 1 48 + } else { 49 + end = middle - 1 50 + } 51 + } 52 + return result 53 + } 54 + 55 + // update tooltip 56 + function mouseEventHandler(param: MouseEventParams) { 57 + values = [] 58 + if ( 59 + param.point === undefined || 60 + !param.time || 61 + param.point.x < 0 || 62 + param.point.x > $chart.container.clientWidth || 63 + param.point.y < 0 || 64 + param.point.y > $chart.container.clientHeight 65 + ) { 66 + return 67 + } 68 + // time is in the same format that we supplied to setData 69 + const time = param.time as UTCTimestamp 70 + 71 + // left = param.point.x + margin 72 + // if (left > $chart.container.clientWidth - width) { 73 + // left = param.point.x - margin - width 74 + // } 75 + 76 + // top = param.point.y + margin 77 + // if (top > $chart.container.clientHeight - height) { 78 + // top = param.point.y - height - margin 79 + // } 80 + 81 + for (const line of $chart.lines) { 82 + const dataPoint = findHighestDataPoint(line.data, time) 83 + if (dataPoint) { 84 + // const date = new Date(dataPoint.t * 1000) 85 + // console.log( 86 + // `${line.name} ${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`, 87 + // dataPoint.v 88 + // ) 89 + values.push({ 90 + line: line, 91 + value: dataPoint.v, 92 + }) 93 + } 94 + } 95 + values.sort((a, b) => b.value - a.value) 96 + values = values 97 + } 98 + $chart.instance.subscribeCrosshairMove(mouseEventHandler) 99 + onDestroy(() => { 100 + $chart.instance.unsubscribeCrosshairMove(mouseEventHandler) 101 + }) 102 + </script> 103 + 104 + <div 105 + class="tooltip" 106 + class:hide={values.length === 0} 107 + style:left={left + 'px'} 108 + style:top={top + 'px'} 109 + bind:clientWidth={width} 110 + bind:clientHeight={height} 111 + > 112 + <table> 113 + <tbody> 114 + {#each values as tooltipValue} 115 + <tr> 116 + <td style:color={hexColors[tooltipValue.line.color]}>{tooltipValue.line.name}</td> 117 + <td class="value"> 118 + {tooltipValue.value} 119 + </td> 120 + </tr> 121 + {/each} 122 + </tbody> 123 + </table> 124 + </div> 125 + 126 + <style lang="sass"> 127 + .tooltip 128 + position: absolute 129 + font-size: 0.8rem 130 + text-align: left 131 + z-index: 10 132 + pointer-events: none 133 + table 134 + padding: 8px 135 + box-sizing: border-box 136 + border: 2px solid 137 + border-radius: 8px 138 + background-color: hsla(230, 0%, 0%, 0.4) 139 + backdrop-filter: blur(3px) 140 + border-color: hsla(241, 74%, 88%, 0.25) 141 + .hide 142 + display: none 143 + .value 144 + text-align: right 145 + padding-left: 2px 146 + font-variant-numeric: tabular-nums 147 + </style>
+1 -3
src/routes/chart.ts
··· 10 10 import { writable } from 'svelte/store' 11 11 import { bottomColors, hexColors, topColors } from './color' 12 12 13 - type DataPoint = { t: UTCTimestamp; v: number } 13 + export type DataPoint = { t: UTCTimestamp; v: number } 14 14 type LineJson = { 15 15 name: string 16 16 data: DataPoint[] ··· 129 129 }, 130 130 131 131 appendLineData(line: Line, data: DataPoint[]) { 132 - const startTime = Date.now() 133 132 if (data.length === 0) { 134 133 return 135 134 } ··· 150 149 if (fresh) { 151 150 store.resetZoom() 152 151 } 153 - console.log(Date.now() - startTime) 154 152 155 153 set(chart) 156 154 },