Эх сурвалжийг харах

v1.1.9

1. 新增品种名显示
2. 调整刷新率
3. 修复视阈显示bug
4. 修复视阈设置bug
5. 是否合并开关
skyffire 1 жил өмнө
parent
commit
c1e8910585

+ 9 - 0
example/main.js

@@ -12,6 +12,7 @@ let memoryCache = {};
 const loginFilePath = path.join(app.getPath('userData'), 'loginSession.json');
 const symbolFilePath = path.join(app.getPath('userData'), 'symbolSession.json');
 const persistentFilePath = path.join(app.getPath('userData'), 'persistentSession.json');
+const mergeFilePath = path.join(app.getPath('userData'), 'mergeSession.json');
 const dbBasePath = path.join(app.getPath('userData'))
 
 function readData(filePath) {
@@ -133,6 +134,14 @@ app.whenReady().then(() => {
     writeData(persistentFilePath, newData);
   });
 
+  ipcMain.handle('get-is-merge-data', () => {
+    return readData(mergeFilePath);
+  });
+
+  ipcMain.handle('set-is-merge-data', (event, newData) => {
+    writeData(mergeFilePath, newData);
+  });
+
   ipcMain.handle('set-db-data', (event, dataList, symbol) => {
     const finalPath = path.join(dbBasePath, `${symbol}.json`)
     writeData(finalPath, dataList)

+ 1 - 1
example/package.json

@@ -1,7 +1,7 @@
 {
   "name": "heatmap",
   "homepage": ".",
-  "version": "1.1.7",
+  "version": "1.1.9",
   "private": true,
   "main": "main.js",
   "scripts": {

+ 20 - 6
example/src/App.js

@@ -101,6 +101,7 @@ export default () => {
   const [activationCode, setActivationCode] = React.useState();
   const [loginSymbol,setLoginSymbol]= React.useState();
   const [isPersistent,setIsPersistent]= React.useState(false);
+  const [isMerge,setIsMerge]= React.useState(false);
   const [loading, setLoading] = React.useState(true);
   const progressRef = React.useRef(null);
   /** @type {React.MutableRefObject<StockHeatmap>} */
@@ -168,6 +169,8 @@ export default () => {
     const symbolInfo = symbolOptions.find((item)=> item.id === loginSymbol)
     const ws = new WebSocket(`ws://localhost:${symbolInfo.port}`);
     let ref = heatmapRef.current
+    ref.symbol = symbolInfo.symbol
+    ref.isMerge = isMerge
     // let prevFlushMemoryDbDataTimestamp = new Date().getTime()
 
     console.log('ws创建完成')
@@ -233,6 +236,7 @@ export default () => {
     const symbolInfo = symbolOptions.find((item)=> item.id === loginSymbol)
     let ref = heatmapRef.current
     ref.data = await window.electronAPI.getDbData(symbolInfo.symbol)
+    ref.windowPosition = Math.max(0, ref.data.length - ref.windowLength)
   }
 
   const handleActivation = async ()=>{
@@ -250,9 +254,9 @@ export default () => {
         checkStatus()
       }, 5000)
       setIsActivation(true)
-      await readLocalDb()
       connectWebSocket()
-      if (isPersistent){
+      if (isPersistent) {
+        await readLocalDb()
         // 每30秒存放一次本地数据
         setInterval(() => {
           saveLocalDb()
@@ -287,6 +291,8 @@ export default () => {
     // 处理持久化选项
     let persistent = await window.electronAPI.getIsPersistentData() || false;
     if (persistent) setIsPersistent(persistent)
+    let isMerge = await window.electronAPI.getIsMergeData() || false;
+    if (isMerge) setIsMerge(isMerge)
 
     const updateFn = () => {
       setWindowDim([
@@ -426,10 +432,18 @@ export default () => {
                   </Option>
                   ))}
               </Select>
-              <Checkbox checked={isPersistent} onChange={async (value)=>{
-                setIsPersistent(value)
-                await window.electronAPI.setIsPersistentData(value)
-              }}>是否持久化数据</Checkbox>
+              <div>
+                <Checkbox checked={isPersistent} onChange={async (value)=>{
+                  setIsPersistent(value)
+                  await window.electronAPI.setIsPersistentData(value)
+                }}>是否持久化数据</Checkbox>
+              </div>
+              <div>
+                <Checkbox checked={isMerge} onChange={async (value)=>{
+                  setIsMerge(value)
+                  await window.electronAPI.setIsMergeData(value)
+                }}>是否开启合并</Checkbox>
+              </div>
             </div>
             <div className="btnWp">
               <div className="btn" onClick={handleActivation}>登 录</div>

+ 2 - 0
example/src/preload.js

@@ -7,6 +7,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
   setSymbolData: (data) => ipcRenderer.invoke('set-symbol-data', data),
   getIsPersistentData: () => ipcRenderer.invoke('get-is-persistent-data'),
   setIsPersistentData: (data) => ipcRenderer.invoke('set-is-persistent-data', data),
+  getIsMergeData: () => ipcRenderer.invoke('get-is-merge-data'),
+  setIsMergeData: (data) => ipcRenderer.invoke('set-is-merge-data', data),
   getDbData: (symbol) => ipcRenderer.invoke('get-db-data', symbol),
   setDbData: (data, symbol) => ipcRenderer.invoke('set-db-data', data, symbol),
   flushMemoryDbData: (data, symbol) => ipcRenderer.invoke('flush-memory-db-data', data, symbol),

+ 49 - 17
src/index.js

@@ -41,7 +41,9 @@ export default class StockHeatmap extends React.Component {
   windowedData = [];
   windowLength = 20;
   windowPosition = 0;
-  isMerged = false;
+  isMerged = false;           // 是否合并了
+  isMerge = false;            // 是否启用合并
+  symbol = 'None'
 
   // 鼠标坐标
   mouse = {
@@ -55,8 +57,8 @@ export default class StockHeatmap extends React.Component {
   // 最大的买卖数量
   maxBidAskVolume = 0;
 
-  // 绘制间隔,120是帧率
-  drawTimestampDistance = parseInt(1000 / 120);
+  // 绘制间隔,60是帧率
+  drawTimestampDistance = parseInt(1000 / 60);
   // 上次绘制时间
   prevDrawTimestamp = 0;
 
@@ -479,12 +481,21 @@ export default class StockHeatmap extends React.Component {
     this.drawingContext.textAlign = 'left';
     this.drawingContext.font = '12px Arial';
 
-    let zoomLevelText = `当前视域:  ${zoomTimeFormat(this.windowLength)}`
-    this.drawingContext.fillText(zoomLevelText, 20, this.defaults.axisTickSize + this.defaults.xAxisTextPadding + 20);
-    let w = this.drawingContext.measureText(zoomLevelText).width;
+    // ========================================= 底部文字绘制 =========================================
+    // 绘制品种
+    let symbol = this.symbol
+    let symbolText = `品种:  ${symbol}`
+    this.drawingContext.fillText(symbolText, 5, this.defaults.axisTickSize + this.defaults.xAxisTextPadding + 20);
+    let w = this.drawingContext.measureText(symbolText).width;
+
+    // 绘制视域
+    let zoomLevelText = `当前视域:  ${zoomTimeFormat(this.windowedData)}   `
+    this.drawingContext.fillText(zoomLevelText, 20 + w + 20, this.defaults.axisTickSize + this.defaults.xAxisTextPadding + 20);
+    w += this.drawingContext.measureText(zoomLevelText).width;
     const maxVolumeInWindowData = extractMaxTradedVolume(this.windowedData);
 
-    const maxVolumeText = `最近${zoomTimeFormat(this.windowLength, 1)}内最大交易量:  `;
+    // 绘制最大交易量
+    const maxVolumeText = `最近${zoomTimeFormat(this.windowedData)}内最大交易量:  `;
     this.drawingContext.fillText(maxVolumeText, 20 + w + 20, this.defaults.axisTickSize + this.defaults.xAxisTextPadding + 20);
     this.drawingContext.fillStyle = this.defaults.textHighlightOnBackground;
     w += this.drawingContext.measureText(maxVolumeText).width;
@@ -492,6 +503,7 @@ export default class StockHeatmap extends React.Component {
     this.drawingContext.fillText(`${maxVolumeInWindowData}`, 20 + w + 20, this.defaults.axisTickSize + this.defaults.xAxisTextPadding + 20);
     w += this.drawingContext.measureText(`${maxVolumeInWindowData}`).width;
 
+    // 最后交易数据的绘制
     let latested = this.windowedData[this.windowedData.length - 1]
     if (this.windowedData.length > 0) {
       this.drawingContext.fillStyle = this.defaults.textOnBackground;
@@ -1038,12 +1050,15 @@ export default class StockHeatmap extends React.Component {
       // move position only if within valid range
       this.windowedData = this.data.slice(position, position + this.windowLength + 1);
 
-      // if (this.windowedData.length > 1000) {
-      //   this.windowedData = this.mergeWindowedData();
-      //   this.isMerged = true;
-      // } else {
-      //   this.isMerged = false;
-      // }
+      // 是否启用合并
+      if (this.isMerge) {
+        if (this.windowedData.length > 1000) {
+          this.windowedData = this.mergeWindowedData();
+          this.isMerged = true;
+        } else {
+          this.isMerged = false;
+        }
+      }
 
       // 延迟日志
       if (this.windowedData.length > 1) {
@@ -1067,10 +1082,27 @@ export default class StockHeatmap extends React.Component {
    * @param {number} zoom The seconds to zoom into
    */
   setZoomLevel = (zoom) => {
-    let l = Math.min(Math.max(zoom * 4, 3), this.data.length - 1);
-    let l2 = this.windowLength - l;
-    this.windowLength = l;
-    this.moveDataWindow(this.windowPosition + l2);
+    if (this.data.length == 0) {
+      return
+    }
+
+    let theoreticalWindowDataLength = 0;                            // 理论数据条数
+    let zoomMillsTimestamp = zoom * 1000;
+
+    let firstIndex = Math.min(this.data.length - 1, this.windowPosition + this.windowLength)
+    let first = this.data[firstIndex]
+    for (let i = firstIndex; i >= 0; i--) {
+      let d = this.data[i]
+      if (first.time - d.time > zoomMillsTimestamp) {
+        break
+      }
+
+      theoreticalWindowDataLength += 1;
+    }
+
+    this.windowLength = Math.max(3, theoreticalWindowDataLength - 1);
+    let pos = Math.max(0, firstIndex - this.windowLength - 1)
+    this.moveDataWindow(pos);
   }
 
   /**

+ 10 - 2
src/utils.js

@@ -129,8 +129,16 @@ export const extractMedianTradedVolume = (data) => {
  * Format zoom scale time
  * @param {number} seconds
  */
-export const zoomTimeFormat = (seconds, decimal) => {
-  if(!decimal) decimal = 2;
+export const zoomTimeFormat = (windowData) => {
+  if (windowData.length == 0) {
+    return '0 分钟'
+  }
+
+  let first = windowData[0]
+  let last = windowData[windowData.length - 1]
+  let seconds = (last.time - first.time) / 1000
+
+  let decimal = 2;
   if(seconds > 59) {
     if(seconds > 3599) {
       let hrs = seconds/3600;