diff --git a/.cspell-partial.json b/.cspell-partial.json
index 9949d2678..3c93c036b 100644
--- a/.cspell-partial.json
+++ b/.cspell-partial.json
@@ -29,6 +29,7 @@
"usecols",
"velb",
"viridis",
- "zdata"
+ "zdata",
+ "Segoe"
]
}
diff --git a/common/autoware_trajectory_kinematics_rviz_plugin/CMakeLists.txt b/common/autoware_trajectory_kinematics_rviz_plugin/CMakeLists.txt
new file mode 100644
index 000000000..bb10ffa42
--- /dev/null
+++ b/common/autoware_trajectory_kinematics_rviz_plugin/CMakeLists.txt
@@ -0,0 +1,31 @@
+cmake_minimum_required(VERSION 3.14)
+project(autoware_trajectory_kinematics_rviz_plugin)
+
+find_package(autoware_cmake REQUIRED)
+autoware_package()
+
+find_package(Qt5 REQUIRED Core Widgets Charts)
+set(QT_LIBRARIES Qt5::Widgets Qt5::Charts)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+add_definitions(-DQT_NO_KEYWORDS)
+
+ament_auto_add_library(${PROJECT_NAME} SHARED
+ src/trajectory_message_adapter.cpp
+ src/trajectory_series_manager.cpp
+ src/trajectory_kinematics_chart_view.cpp
+ src/trajectory_kinematics_plot_widget.cpp
+ src/trajectory_kinematics_panel.cpp
+)
+
+target_link_libraries(${PROJECT_NAME}
+ ${QT_LIBRARIES}
+)
+
+pluginlib_export_plugin_description_file(rviz_common plugins/plugin_description.xml)
+
+ament_auto_package(
+ INSTALL_TO_SHARE
+ icons
+ plugins
+)
diff --git a/common/autoware_trajectory_kinematics_rviz_plugin/README.md b/common/autoware_trajectory_kinematics_rviz_plugin/README.md
new file mode 100644
index 000000000..09056c122
--- /dev/null
+++ b/common/autoware_trajectory_kinematics_rviz_plugin/README.md
@@ -0,0 +1,23 @@
+# autoware_trajectory_kinematics_rviz_plugin
+
+RViz2 **panel plugin** that plots kinematic quantities along planned paths and compares **multiple trajectories** on one Qt Charts graph.
+
+## Features
+
+- Subscribe to one or more topics publishing:
+ - `autoware_planning_msgs/msg/Trajectory`, or
+ - `autoware_internal_planning_msgs/msg/ScoredCandidateTrajectories`
+- Choose **X** (time from start or arc length) and **Y** (velocity, acceleration, curvature, lateral acceleration).
+- Checkbox which series to plot; optional **fixed X/Y axis ranges**; hover tooltip with crosshair.
+- Panel layout and topic list are stored in the RViz config (`save` / `load`).
+
+## Usage
+
+1. Build the workspace with this package in the overlay (standard `colcon build`).
+2. Start RViz2, **Add Panel** → select **`autoware_trajectory_kinematics_rviz_plugin/TrajectoryKinematicsPanel`**.
+3. Choose message kind, add topics (dropdown or “Other topic”), tick trajectories to plot, set axes.
+
+## ROS interfaces
+
+- **Subscribes** (user-configured): topics you add, as `Trajectory` or `ScoredCandidateTrajectories` depending on the kind selector.
+- **Does not publish** topics.
diff --git a/common/autoware_trajectory_kinematics_rviz_plugin/icons/classes/TrajectoryKinematicsPanel.png b/common/autoware_trajectory_kinematics_rviz_plugin/icons/classes/TrajectoryKinematicsPanel.png
new file mode 100644
index 000000000..6a6757371
Binary files /dev/null and b/common/autoware_trajectory_kinematics_rviz_plugin/icons/classes/TrajectoryKinematicsPanel.png differ
diff --git a/common/autoware_trajectory_kinematics_rviz_plugin/package.xml b/common/autoware_trajectory_kinematics_rviz_plugin/package.xml
new file mode 100644
index 000000000..f3eaa5924
--- /dev/null
+++ b/common/autoware_trajectory_kinematics_rviz_plugin/package.xml
@@ -0,0 +1,27 @@
+
+
+
+ autoware_trajectory_kinematics_rviz_plugin
+ 0.46.0
+ RViz2 panel to plot trajectory kinematics (velocity, acceleration, curvature, lateral acceleration) from Trajectory or ScoredCandidateTrajectories topics.
+ Taiki Tanaka
+ Apache License 2.0
+
+ ament_cmake_auto
+ autoware_cmake
+
+ autoware_internal_planning_msgs
+ autoware_planning_msgs
+ libqt5-charts-dev
+ pluginlib
+ rclcpp
+ rviz_common
+
+ ament_cmake_gtest
+ ament_lint_auto
+ autoware_lint_common
+
+
+ ament_cmake
+
+
diff --git a/common/autoware_trajectory_kinematics_rviz_plugin/plugins/plugin_description.xml b/common/autoware_trajectory_kinematics_rviz_plugin/plugins/plugin_description.xml
new file mode 100644
index 000000000..7601ba863
--- /dev/null
+++ b/common/autoware_trajectory_kinematics_rviz_plugin/plugins/plugin_description.xml
@@ -0,0 +1,8 @@
+
+
+ Plot kinematics along autoware_planning_msgs/Trajectory or autoware_internal_planning_msgs/ScoredCandidateTrajectories (multi-series comparison).
+
+
diff --git a/common/autoware_trajectory_kinematics_rviz_plugin/src/kinematics_types.hpp b/common/autoware_trajectory_kinematics_rviz_plugin/src/kinematics_types.hpp
new file mode 100644
index 000000000..60c766086
--- /dev/null
+++ b/common/autoware_trajectory_kinematics_rviz_plugin/src/kinematics_types.hpp
@@ -0,0 +1,156 @@
+// Copyright 2026 TIER IV, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef KINEMATICS_TYPES_HPP_
+#define KINEMATICS_TYPES_HPP_
+
+#include
+#include
+#include
+
+namespace autoware::visualization::trajectory_kinematics_rviz_plugin
+{
+
+/// @brief One sample along a trajectory polyline with derived kinematics for plotting.
+struct SeriesPoint
+{
+ double time_from_start_sec{0.0};
+ double arc_length_m{0.0};
+ double longitudinal_velocity_mps{0.0};
+ double acceleration_mps2{0.0};
+ /// Signed planar curvature [1/m] (Menger estimate on XY).
+ double curvature_1pm{0.0};
+ /// Approximate lateral acceleration [m/s²] using a_lat ≈ v_x² κ (planar bicycle-style estimate).
+ double lateral_acceleration_mps2{0.0};
+};
+
+/// @brief A plottable series: ROS topic, logical key, human-readable label, and sampled points.
+struct TrajectorySeriesData
+{
+ std::string topic;
+ /// Stable id within the topic (e.g. "trajectory", "candidate_3").
+ std::string key;
+ std::string label;
+ std::vector points;
+};
+
+/// @brief Selects which scalar is read from SeriesPoint for an axis or tooltip.
+enum class AxisId : std::uint8_t {
+ TIME_FROM_START = 0,
+ ARC_LENGTH,
+ LONGITUDINAL_VELOCITY,
+ ACCELERATION,
+ CURVATURE,
+ LATERAL_ACCELERATION,
+};
+
+/// @brief Human-readable labels and units for axis titles and tooltips.
+struct AxisDefinition
+{
+ AxisId id;
+ const char * label;
+ const char * unit;
+};
+
+/// @brief X-axis choices (time or arc length).
+inline const std::vector & xAxisDefinitions()
+{
+ static const std::vector defs = {
+ {AxisId::TIME_FROM_START, "Time from start", "s"},
+ {AxisId::ARC_LENGTH, "Arc length", "m"},
+ };
+ return defs;
+}
+
+/// @brief Y-axis choices (longitudinal/lateral motion and curvature).
+inline const std::vector & yAxisDefinitions()
+{
+ static const std::vector defs = {
+ {AxisId::LONGITUDINAL_VELOCITY, "Longitudinal velocity", "m/s"},
+ {AxisId::ACCELERATION, "Acceleration", "m/s²"},
+ {AxisId::CURVATURE, "Curvature (signed)", "1/m"},
+ {AxisId::LATERAL_ACCELERATION, "Lateral acceleration (v²κ)", "m/s²"},
+ };
+ return defs;
+}
+
+/// @brief Maps a kinematics axis id to the corresponding field in `p`.
+inline double accessAxisValue(const SeriesPoint & p, AxisId id)
+{
+ switch (id) {
+ case AxisId::TIME_FROM_START:
+ return p.time_from_start_sec;
+ case AxisId::ARC_LENGTH:
+ return p.arc_length_m;
+ case AxisId::LONGITUDINAL_VELOCITY:
+ return p.longitudinal_velocity_mps;
+ case AxisId::ACCELERATION:
+ return p.acceleration_mps2;
+ case AxisId::CURVATURE:
+ return p.curvature_1pm;
+ case AxisId::LATERAL_ACCELERATION:
+ return p.lateral_acceleration_mps2;
+ }
+#if defined(__GNUC__) || defined(__clang__)
+ __builtin_unreachable();
+#else
+ return 0.0;
+#endif
+}
+
+/// @brief Looks up axis metadata from xAxisDefinitions() then yAxisDefinitions().
+inline const AxisDefinition * findAxisDefinition(AxisId id)
+{
+ for (const auto & d : xAxisDefinitions()) {
+ if (d.id == id) {
+ return &d;
+ }
+ }
+ for (const auto & d : yAxisDefinitions()) {
+ if (d.id == id) {
+ return &d;
+ }
+ }
+ return nullptr;
+}
+
+/// @brief Returns the display label for `id`, or `"?"` if unknown.
+inline const char * axisLabel(AxisId id)
+{
+ const AxisDefinition * d = findAxisDefinition(id);
+ return d ? d->label : "?";
+}
+
+/// @brief Returns the unit string for `id`, or empty if unknown.
+inline const char * axisUnit(AxisId id)
+{
+ const AxisDefinition * d = findAxisDefinition(id);
+ return d ? d->unit : "";
+}
+
+/// @brief Optional fixed axis ranges for the plot (honored only when the corresponding lock flag is
+/// true).
+struct PlotAxisRangeOptions
+{
+ bool lock_x{false};
+ double x_min{0.0};
+ double x_max{1.0};
+ bool lock_y{false};
+ double y_min{-1.0};
+ double y_max{1.0};
+};
+
+} // namespace autoware::visualization::trajectory_kinematics_rviz_plugin
+
+#endif // KINEMATICS_TYPES_HPP_
diff --git a/common/autoware_trajectory_kinematics_rviz_plugin/src/material_colors.hpp b/common/autoware_trajectory_kinematics_rviz_plugin/src/material_colors.hpp
new file mode 100644
index 000000000..97b68da6c
--- /dev/null
+++ b/common/autoware_trajectory_kinematics_rviz_plugin/src/material_colors.hpp
@@ -0,0 +1,113 @@
+// Copyright 2026 TIER IV, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Duplicated from tier4_state_rviz_plugin/src/include/material_colors.hpp for a self-contained
+// dependency graph. Palette matches AutowareStatePanel styling.
+
+#ifndef MATERIAL_COLORS_HPP_
+#define MATERIAL_COLORS_HPP_
+
+#include
+
+#include
+
+namespace autoware::visualization::trajectory_kinematics_rviz_plugin::style
+{
+
+/// @brief Material Design 3–style palette as hex strings for RViz panel QSS and QColor
+/// construction.
+struct MaterialColors
+{
+ std::string primary = "#8BD0F0";
+ std::string surface_tint = "#8BD0F0";
+ std::string on_primary = "#003546";
+ std::string primary_container = "#004D64";
+ std::string on_primary_container = "#BEE9FF";
+ std::string secondary = "#B4CAD6";
+ std::string on_secondary = "#1F333C";
+ std::string secondary_container = "#354A54";
+ std::string on_secondary_container = "#D0E6F2";
+ std::string tertiary = "#C6C2EA";
+ std::string on_tertiary = "#2F2D4D";
+ std::string tertiary_container = "#454364";
+ std::string on_tertiary_container = "#E3DFFF";
+ std::string error = "#FFB4AB";
+ std::string on_error = "#690005";
+ std::string error_container = "#820008";
+ std::string error_press = "#982127";
+ std::string on_error_container = "#8c0f16";
+ std::string background = "#0F1417";
+ std::string on_background = "#DFE3E7";
+ std::string surface = "#0F1417";
+ std::string on_surface = "#DFE3E7";
+ std::string surface_variant = "#40484C";
+ std::string on_surface_variant = "#C0C8CD";
+ std::string outline = "#8A9297";
+ std::string outline_variant = "#40484C";
+ std::string shadow = "#000000";
+ std::string scrim = "#000000";
+ std::string inverse_surface = "#DFE3E7";
+ std::string inverse_on_surface = "#2C3134";
+ std::string inverse_primary = "#126682";
+ std::string primary_fixed = "#BEE9FF";
+ std::string on_primary_fixed = "#001F2A";
+ std::string primary_fixed_dim = "#8BD0F0";
+ std::string on_primary_fixed_variant = "#004D64";
+ std::string secondary_fixed = "#D0E6F2";
+ std::string on_secondary_fixed = "#081E27";
+ std::string secondary_fixed_dim = "#B4CAD6";
+ std::string on_secondary_fixed_variant = "#354A54";
+ std::string tertiary_fixed = "#E3DFFF";
+ std::string on_tertiary_fixed = "#1A1836";
+ std::string tertiary_fixed_dim = "#C6C2EA";
+ std::string on_tertiary_fixed_variant = "#454364";
+ std::string surface_dim = "#0F1417";
+ std::string surface_bright = "#353A3D";
+ std::string surface_container_lowest = "#0A0F11";
+ std::string surface_container_low = "#171C1F";
+ std::string surface_container = "#1B2023";
+ std::string surface_container_high = "#262B2E";
+ std::string surface_container_highest = "#303538";
+ std::string disabled_elevated_button_bg = "#292D30";
+ std::string success = "#8DF08B";
+ std::string warning = "#EEF08B";
+ std::string info = "#8BD0F0";
+ std::string danger = "#F08B8B";
+
+ std::string enabled_button_bg = "#8BD0F0";
+ std::string hover_button_bg = "#84c2e6";
+ std::string pressed_button_bg = "#699BB8";
+ std::string checked_button_bg = "#699BB8";
+ std::string disabled_button_bg = "#292d30";
+ std::string disabled_button_text = "#6e7276";
+
+ std::string on_surface_hover_bg = "#212429";
+ std::string on_surface_pressed_bg = "#292d32";
+ std::string on_surface_disabled = "#5e6266";
+
+ std::string surface_container_low_hover = "#262931";
+ std::string surface_container_low_pressed = "#2d303a";
+};
+
+inline const MaterialColors default_colors{};
+
+/// Convert a CSS-style `#RGB` string from the palette to `QColor`.
+inline QColor hexToQColor(const std::string & hex)
+{
+ return QColor(QString::fromStdString(hex));
+}
+
+} // namespace autoware::visualization::trajectory_kinematics_rviz_plugin::style
+
+#endif // MATERIAL_COLORS_HPP_
diff --git a/common/autoware_trajectory_kinematics_rviz_plugin/src/trajectory_kinematics_chart_view.cpp b/common/autoware_trajectory_kinematics_rviz_plugin/src/trajectory_kinematics_chart_view.cpp
new file mode 100644
index 000000000..21ea5b7c4
--- /dev/null
+++ b/common/autoware_trajectory_kinematics_rviz_plugin/src/trajectory_kinematics_chart_view.cpp
@@ -0,0 +1,275 @@
+// Copyright 2026 TIER IV, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "trajectory_kinematics_chart_view.hpp"
+
+#include "material_colors.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+namespace autoware::visualization::trajectory_kinematics_rviz_plugin
+{
+namespace
+{
+
+/// Plot X axis is always time or arc length; both are non-decreasing along sampled points.
+bool xAxisIsMonotonicInSeries(AxisId x_axis)
+{
+ return x_axis == AxisId::TIME_FROM_START || x_axis == AxisId::ARC_LENGTH;
+}
+
+size_t nearestPointIndexLinear(const TrajectorySeriesData & s, AxisId x_axis, double x_target)
+{
+ if (s.points.empty()) {
+ return 0;
+ }
+ size_t best = 0;
+ double best_d = std::numeric_limits::infinity();
+ for (size_t i = 0; i < s.points.size(); ++i) {
+ const double xv = accessAxisValue(s.points[i], x_axis);
+ if (!std::isfinite(xv)) {
+ continue;
+ }
+ const double d = std::abs(xv - x_target);
+ if (d < best_d) {
+ best_d = d;
+ best = i;
+ }
+ }
+ return best;
+}
+
+size_t nearestPointIndexMonotonic(const TrajectorySeriesData & s, AxisId x_axis, double x_target)
+{
+ if (s.points.empty()) {
+ return 0;
+ }
+ const auto & pts = s.points;
+ auto less_x = [x_axis](const SeriesPoint & p, double xv) {
+ return accessAxisValue(p, x_axis) < xv;
+ };
+ auto it = std::lower_bound(pts.begin(), pts.end(), x_target, less_x);
+ if (it == pts.begin()) {
+ return 0;
+ }
+ if (it == pts.end()) {
+ return pts.size() - 1;
+ }
+ auto prev = it - 1;
+ const double d0 = std::abs(accessAxisValue(*prev, x_axis) - x_target);
+ const double d1 = std::abs(accessAxisValue(*it, x_axis) - x_target);
+ return d0 <= d1 ? static_cast(std::distance(pts.begin(), prev))
+ : static_cast(std::distance(pts.begin(), it));
+}
+
+size_t nearestPointIndexForSeries(const TrajectorySeriesData & s, AxisId x_axis, double x_target)
+{
+ if (xAxisIsMonotonicInSeries(x_axis)) {
+ return nearestPointIndexMonotonic(s, x_axis, x_target);
+ }
+ return nearestPointIndexLinear(s, x_axis, x_target);
+}
+
+QtCharts::QLineSeries * firstDataLineSeries(
+ QtCharts::QChart * ch, const QtCharts::QLineSeries * crosshair)
+{
+ if (ch == nullptr) {
+ return nullptr;
+ }
+ for (QtCharts::QAbstractSeries * ser : ch->series()) {
+ auto * ls = qobject_cast(ser);
+ if (ls == nullptr || ls == crosshair) {
+ continue;
+ }
+ return ls;
+ }
+ return nullptr;
+}
+
+constexpr int kTooltipMinIntervalMs = 50;
+constexpr double kTooltipSameXEps = 1e-7;
+
+} // namespace
+
+TrajectoryKinematicsChartView::TrajectoryKinematicsChartView(QWidget * parent)
+: QtCharts::QChartView(parent)
+{
+ setMouseTracking(true);
+ setRubberBand(QtCharts::QChartView::NoRubberBand);
+
+ const auto & pal = style::default_colors;
+ setStyleSheet(QStringLiteral(
+ "QToolTip {"
+ " background-color: %1;"
+ " color: %2;"
+ " border: 1px solid %3;"
+ " border-radius: 6px;"
+ " padding: 6px 8px;"
+ " font-size: 11px;"
+ "}")
+ .arg(QString::fromStdString(pal.surface_container_high))
+ .arg(QString::fromStdString(pal.on_surface))
+ .arg(QString::fromStdString(pal.outline_variant)));
+}
+
+void TrajectoryKinematicsChartView::setCrosshairSeries(QtCharts::QLineSeries * series)
+{
+ crosshair_series_ = series;
+}
+
+void TrajectoryKinematicsChartView::setHoverSeriesData(
+ const std::vector & series, const std::vector & colors,
+ AxisId x_axis, AxisId y_axis)
+{
+ hover_series_ = series;
+ hover_colors_ = colors;
+ x_axis_ = x_axis;
+ y_axis_ = y_axis;
+}
+
+void TrajectoryKinematicsChartView::mouseMoveEvent(QMouseEvent * event)
+{
+ QtCharts::QChartView::mouseMoveEvent(event);
+
+ auto * ch = chart();
+ if (hover_series_.empty() || ch == nullptr) {
+ QToolTip::hideText();
+ if (crosshair_series_) {
+ crosshair_series_->clear();
+ }
+ return;
+ }
+
+ auto * ref_series = firstDataLineSeries(ch, crosshair_series_);
+ if (ref_series == nullptr) {
+ QToolTip::hideText();
+ if (crosshair_series_) {
+ crosshair_series_->clear();
+ }
+ return;
+ }
+
+ // Widget → scene → chart item space: required before QChart::mapToValue for correct axis
+ // mapping.
+ const QPointF scene_pos = mapToScene(event->pos());
+ const QPointF chart_pos = ch->mapFromScene(scene_pos);
+ if (!ch->plotArea().contains(chart_pos)) {
+ QToolTip::hideText();
+ if (crosshair_series_) {
+ crosshair_series_->clear();
+ }
+ return;
+ }
+
+ // mapToValue uses the attached axes; ref_series must be a data line (not the crosshair) so X
+ // is read in plot units.
+ const QPointF value = ch->mapToValue(chart_pos, ref_series);
+ const double x_cursor = value.x();
+ if (!std::isfinite(x_cursor)) {
+ QToolTip::hideText();
+ if (crosshair_series_) {
+ crosshair_series_->clear();
+ }
+ return;
+ }
+
+ const QList vaxes = ch->axes(Qt::Vertical);
+ auto * ay = qobject_cast(vaxes.isEmpty() ? nullptr : vaxes.first());
+ if (crosshair_series_ && ay != nullptr) {
+ const double y_lo = ay->min();
+ const double y_hi = ay->max();
+ crosshair_series_->clear();
+ crosshair_series_->append(x_cursor, y_lo);
+ crosshair_series_->append(x_cursor, y_hi);
+ }
+
+ // Throttle rich HTML tooltip updates: crosshair still moves, but QString rebuilds are skipped at
+ // high pointer rate.
+ const bool same_x =
+ last_tooltip_x_valid_ && std::abs(x_cursor - last_tooltip_x_) <= kTooltipSameXEps;
+ const bool within_rate =
+ tooltip_rate_timer_.isValid() && tooltip_rate_timer_.elapsed() < kTooltipMinIntervalMs;
+ if (same_x && within_rate) {
+ return;
+ }
+ tooltip_rate_timer_.restart();
+ last_tooltip_x_ = x_cursor;
+ last_tooltip_x_valid_ = true;
+
+ const auto & pal = style::default_colors;
+ const QString bg = QString::fromStdString(pal.surface_container_high);
+ const QString fg = QString::fromStdString(pal.on_surface);
+ const QString muted = QString::fromStdString(pal.on_surface_variant);
+
+ QString html;
+ html += QStringLiteral(
+ "")
+ .arg(bg)
+ .arg(fg);
+ html += QStringLiteral(
+ "%1 = %2 "
+ "%3
")
+ .arg(QString::fromUtf8(axisLabel(x_axis_)))
+ .arg(x_cursor, 0, 'f', 4)
+ .arg(QString::fromUtf8(axisUnit(x_axis_)))
+ .arg(muted);
+
+ for (size_t i = 0; i < hover_series_.size(); ++i) {
+ const auto & s = hover_series_[i];
+ if (s.points.empty()) {
+ continue;
+ }
+ const size_t idx = nearestPointIndexForSeries(s, x_axis_, x_cursor);
+ const double yv = accessAxisValue(s.points[idx], y_axis_);
+ const QColor c = (i < hover_colors_.size()) ? hover_colors_[i] : QColor(200, 200, 200);
+ html += QStringLiteral(
+ ""
+ "■"
+ " %2 %3"
+ "
")
+ .arg(c.name(QColor::HexRgb))
+ .arg(yv, 0, 'f', 4)
+ .arg(QString::fromUtf8(axisUnit(y_axis_)))
+ .arg(muted)
+ .arg(fg);
+ }
+ html += QStringLiteral("");
+
+ QToolTip::showText(event->globalPos(), html, this, QRect(), 8000);
+}
+
+void TrajectoryKinematicsChartView::leaveEvent(QEvent * event)
+{
+ QToolTip::hideText();
+ last_tooltip_x_valid_ = false;
+ if (crosshair_series_) {
+ crosshair_series_->clear();
+ }
+ QtCharts::QChartView::leaveEvent(event);
+}
+
+} // namespace autoware::visualization::trajectory_kinematics_rviz_plugin
diff --git a/common/autoware_trajectory_kinematics_rviz_plugin/src/trajectory_kinematics_chart_view.hpp b/common/autoware_trajectory_kinematics_rviz_plugin/src/trajectory_kinematics_chart_view.hpp
new file mode 100644
index 000000000..6e64c5ea1
--- /dev/null
+++ b/common/autoware_trajectory_kinematics_rviz_plugin/src/trajectory_kinematics_chart_view.hpp
@@ -0,0 +1,80 @@
+// Copyright 2026 TIER IV, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef TRAJECTORY_KINEMATICS_CHART_VIEW_HPP_
+#define TRAJECTORY_KINEMATICS_CHART_VIEW_HPP_
+
+#include "kinematics_types.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace autoware::visualization::trajectory_kinematics_rviz_plugin
+{
+
+/// @brief Qt Charts view with a vertical crosshair and a multi-series HTML tooltip at the pointer
+/// X.
+///
+/// Maps mouse position to plot coordinates via QChart::mapToValue(), finds the nearest sample per
+/// series along the current X axis (binary search when that axis is monotonic in point order), and
+/// throttles tooltip updates to reduce overhead during motion.
+class TrajectoryKinematicsChartView : public QtCharts::QChartView
+{
+ Q_OBJECT
+
+public:
+ /// @brief Enables mouse tracking and styles tooltips to match the panel palette.
+ explicit TrajectoryKinematicsChartView(QWidget * parent = nullptr);
+
+ /// @brief Registers the dedicated line series drawn as the vertical crosshair (updated on hover).
+ void setCrosshairSeries(QtCharts::QLineSeries * series);
+
+ /// @brief Supplies the trajectories and colors used for tooltip content and nearest-point lookup.
+ /// @param series Same trajectories currently plotted (order matches `colors`).
+ /// @param colors Swatch colors for HTML rows (may be shorter than `series`; a neutral gray is
+ /// used if missing).
+ /// @param x_axis Axis used for cursor X and for choosing the nearest point index in each series.
+ /// @param y_axis Axis whose value is shown per series at that index.
+ void setHoverSeriesData(
+ const std::vector & series, const std::vector & colors,
+ AxisId x_axis, AxisId y_axis);
+
+protected:
+ /// @brief Updates crosshair endpoints, optional HTML tooltip, and rate limiting state.
+ void mouseMoveEvent(QMouseEvent * event) override;
+
+ /// @brief Hides the tooltip and clears the crosshair when the pointer leaves the view.
+ void leaveEvent(QEvent * event) override;
+
+private:
+ std::vector hover_series_;
+ std::vector hover_colors_;
+ AxisId x_axis_{AxisId::TIME_FROM_START};
+ AxisId y_axis_{AxisId::LONGITUDINAL_VELOCITY};
+
+ QPointer crosshair_series_;
+
+ QElapsedTimer tooltip_rate_timer_;
+ double last_tooltip_x_{0.0};
+ bool last_tooltip_x_valid_{false};
+};
+
+} // namespace autoware::visualization::trajectory_kinematics_rviz_plugin
+
+#endif // TRAJECTORY_KINEMATICS_CHART_VIEW_HPP_
diff --git a/common/autoware_trajectory_kinematics_rviz_plugin/src/trajectory_kinematics_panel.cpp b/common/autoware_trajectory_kinematics_rviz_plugin/src/trajectory_kinematics_panel.cpp
new file mode 100644
index 000000000..1c3d0303a
--- /dev/null
+++ b/common/autoware_trajectory_kinematics_rviz_plugin/src/trajectory_kinematics_panel.cpp
@@ -0,0 +1,811 @@
+// Copyright 2026 TIER IV, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "trajectory_kinematics_panel.hpp"
+
+#include "material_colors.hpp"
+#include "ui_font.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include