VisActor / VisActor/VChart

[Feature] linear/time x-axis chart can select only one tooltip datum

Open
#4,193 1 comment 0 reactions 1 assignee View on GitHub

@xuefei1313 is already working on this.

Since Oct 15, 2025.

enhancement tooltip
Dominant language
TypeScript
Stars
1.8k
Forks
221
Avg merge
1d 6h
Merged PRs (30d)
26

Description

What problem does this feature solve?
  const spec = {
    theme: {
      name: 'green',
      type: 'light',
      fontFamily: 'TikTok Text,TikTokFont',
      colorScheme: {
        default: [
          'rgb(0, 153, 149)',
          'rgb(254, 194, 76)',
          'rgb(0, 99, 190)',
          'rgb(0, 202, 177)',
          'rgb(153, 136, 235)',
          'rgb(0, 116, 73)',
          'rgb(0, 164, 243)'
        ],
        funnel: ['#028380', '#009995', '#30ACA9', '#5DBEBB', '#88CFCD', '#B1E0DD', '#D9F0EE']
      },
      background: '#FFFFFF',
      component: {
        title: {
          textStyle: {
            fill: 'rgba(0, 0, 0, 0.92)'
          }
        },
        tooltip: {
          panel: {
            backgroundColor: 'rgba(255,255,255,0.95)'
          },
          shape: {
            shapeType: 'circle'
          },
          titleLabel: {
            fontSize: 12,
            fontWeight: 500,
            fill: 'rgba(0, 0, 0, 0.92)',
            fontFamily: 'TikTok Text,TikTokFont'
          },
          keyLabel: {
            fontSize: 12,
            fontWeight: 'normal',
            fill: 'rgba(0, 0, 0, 0.92)',
            fontFamily: 'TikTok Text,TikTokFont'
          },
          valueLabel: {
            fontSize: 12,
            fontWeight: 500,
            fill: 'rgba(0, 0, 0, 0.92)',
            fontFamily: 'TikTok Text,TikTokFont'
          }
        },
        dataZoom: {
          background: {
            fill: 'rgba(0, 0, 0, 0.35)'
          },
          backgroundChart: {
            area: {
              fill: 'rgba(67,142,255,0.3)'
            }
          },
          selectedBackground: {
            fill: 'rgba(67,142,255, 0.1)'
          },
          startHandler: {
            fill: 'rgb(0, 153, 149)'
          },
          endHandler: {
            fill: 'rgb(0, 153, 149)'
          }
        },
        brush: {
          style: {
            fill: 'rgba(67,142,255,0.3)'
          }
        },
        axisBand: {
          tick: {
            style: {
              stroke: 'rgba(0, 0, 0, 0.14)'
            }
          },
          domainLine: {
            style: {
              stroke: 'rgba(0, 0, 0, 0.04)'
            }
          },
          label: {
            style: {
              fill: 'rgba(0, 0, 0, 0.35)'
            }
          },
          title: {
            style: {
              fill: 'rgba(0, 0, 0, 0.92)'
            }
          },
          grid: {
            style: {
              stroke: 'rgba(0, 0, 0, 0.1)'
            }
          }
        },
        axisLinear: {
          tick: {
            style: {
              stroke: 'rgba(0, 0, 0, 0.14)'
            }
          },
          domainLine: {
            style: {
              stroke: 'rgba(0, 0, 0, 0.04)'
            }
          },
          label: {
            style: {
              fill: 'rgba(0, 0, 0, 0.35)'
            }
          },
          title: {
            style: {
              fill: 'rgba(0, 0, 0, 0.92)'
            }
          },
          grid: {
            style: {
              stroke: 'rgba(0, 0, 0, 0.1)'
            }
          }
        },
        discreteLegend: {
          padding: [16, 0],
          item: {
            label: {
              style: {
                fill: 'rgba(0, 0, 0, 0.55)'
              },
              state: {
                unSelected: {
                  fill: 'rgba(0, 0, 0, 0.35)'
                }
              }
            },
            shape: {
              style: {
                symbolType: 'circle',
                size: 8
              }
            }
          },
          pager: {
            handler: {
              style: {
                fill: 'rgb(0, 153, 149)'
              },
              state: {
                disable: {
                  fill: 'rgb(240, 240, 240)'
                }
              }
            }
          }
        },
        markLine: {
          line: {
            style: {
              lineDash: [0],
              stroke: 'rgba(0, 0, 0, 0.14)'
            }
          }
        },
        markPoint: {
          itemContent: {
            text: {
              style: {
                fill: 'rgba(0, 0, 0, 0.55)',
                fontFamily: 'TikTok Text,TikTokFont'
              },
              labelBackground: {
                style: {
                  fill: 'rgba(240, 240, 240, 1)'
                }
              }
            }
          }
        }
      },
      series: {
        gauge: {
          segment: {
            style: {
              fill: 'rgba(0, 0, 0, 0.1)'
            }
          }
        },
        map: {
          area: {
            state: {
              hover: {
                fill: 'rgb(0, 153, 149)'
              }
            }
          }
        },
        scatter: {
          label: {
            visible: false,
            offset: 7,
            position: 'bottom',
            style: {
              fontSize: 12,
              lineWidth: 2,
              fill: 'rgba(0, 0, 0, 0.55)'
            }
          }
        },
        funnel: {
          outerLabel: {
            line: {
              style: {
                fontSize: 12,
                stroke: 'rgba(0, 0, 0, 0.1)'
              }
            }
          }
        },
        bar: {
          label: {
            style: {
              fontSize: 12,
              fill: 'rgba(0, 0, 0, 0.35)'
            }
          }
        }
      }
    },
    type: 'common',
    seriesField: 'color',
    legends: {
      orient: 'top',
      position: 'start',
      style: {
        dy: -12
      },
      item: {
        shape: {
          space: 4,
          style: {
            size: 10,
            symbolType: 'circle'
          }
        }
      }
    },
    data: [
      {
        id: 'data-0',
        values: [
          {
            'Final Price': 78,
            tag: 'Final Price',
            time: '09/12/2025 12:00 AM',
            'Final Price-amount': '78',
            'Final Price-amountFormatted': '$78.00'
          },
          {
            'Final Price': 78,
            tag: 'Final Price',
            time: '09/12/2025 7:06 PM',
            'Final Price-amount': '78',
            'Final Price-amountFormatted': '$78.00'
          },
          {
            'Final Price': 88,
            tag: 'Final Price',
            time: '09/15/2025 7:18 PM',
            'Final Price-amount': '88',
            'Final Price-amountFormatted': '$88.00'
          },
          {
            'Final Price': 32.11,
            tag: 'Final Price',
            time: '09/21/2025 12:00 AM',
            'Final Price-amount': '32.11',
            'Final Price-amountFormatted': '$32.11'
          },
          {
            'Final Price': 88,
            tag: 'Final Price',
            time: '09/22/2025 12:00 AM',
            'Final Price-amount': '88',
            'Final Price-amountFormatted': '$88.00'
          },
          {
            'Final Price': 88,
            tag: 'Final Price',
            time: '09/26/2025 12:00 AM',
            'Final Price-amount': '88',
            'Final Price-amountFormatted': '$88.00'
          }
        ].map(entry => {
          return {
            ...entry,
            time: new Date(entry.time).getTime()
          };
        })
      }
    ],
    padding: [0, 0, 2, 8],
    tooltip: {
      mark: {
        position: 'left',
        title: {
          value: 'Final Price'
        }
      },
      dimension: {
        position: 'left',
        title: {
          value: 'Final Price'
        },
        content: {
          key: datum => {
            return formatTimestampToYMDHM(datum?.time);
          },
          value: datum => {
            return datum?.['Final Price-amountFormatted'];
          }
        }
      }
    },
    crosshair: {
      xField: {
        visible: true,
        line: {
          visible: true,
          type: 'line'
        }
      }
    },
    axes: [
      {
        id: 'leftYAxis',
        orient: 'left',
        tickCount: 5,
        exactTickCount: true,
        zero: false,
        label: {},
        seriesId: ['line-0-Final Price']
      },
      {
        orient: 'bottom',
        label: {
          visible: true
        },
        // type: 'linear',
        type: 'time',
        layers: [
          {
            tickStep: 86400,
            timeFormat: '%m/%d/%Y'
          }
        ],
        // -86400000
        tooltipFilterRange: [-1 * 60 * 60 * 1000, 0]
      }
    ],
    series: [
      {
        type: 'line',
        id: 'line-0-Final Price',
        dataId: 'data-0',
        name: 'Final Price',
        seriesField: 'tag',
        xField: 'time',
        yField: 'Final Price',
        stack: false,
        point: {
          visible: false,
          state: {
            dimension_hover: {
              stroke: '#fff',
              shadowColor: 'rgba(0, 0, 0, 0.08)',
              shadowOffsetY: 2,
              lineWidth: 2
            }
          }
        },
        activePoint: true,
        line: {
          style: {
            curveType: 'stepAfter'
          }
        }
      }
    ],
    color: {
      type: 'ordinal',
      domain: ['Final Price'],
      range: [
        'rgb(0, 153, 149)',
        'rgb(254, 194, 76)',
        'rgb(0, 99, 190)',
        'rgb(0, 202, 177)',
        'rgb(153, 136, 235)',
        'rgb(0, 116, 73)',
        'rgb(0, 164, 243)'
      ]
    },
    markLine: [
      {
        y: 100,
        label: {
          position: 'insideStartTop',
          text: 'Original Price',
          labelBackground: {
            visible: false,
            style: {
              fill: 'rgba(0, 0, 0, 0.92)',
              fontSize: 12,
              lineHeight: 18,
              fontWeight: 400
            }
          }
        },
        line: {
          style: {
            stroke: 'rgba(0, 0, 0, 0.55)',
            lineDash: [2, 3]
          }
        },
        startSymbol: {
          visible: true,
          symbolType: 'triangleDown'
        },
        endSymbol: {
          visible: false
        },
        autoRange: true
      }
    ]
  };

const vchart = new VChart(spec, { dom: CONTAINER_ID });
vchart.renderSync();

// Just for the convenience of console debugging, DO NOT COPY!
window['vchart'] = vchart;
  1. 希望hover 的时候,只高亮左侧的一个点
What does the proposed API look like?
{
   tooltipFilter: ( tooltipValue: number, datumValue, datum,  field) => boolean
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.