diff --git a/packages/client/src/components/chart-tooltip.vue b/packages/client/src/components/chart-tooltip.vue
index b080eaf2b45899ed22803289aaafc5d4a2731e70..20e094a5a7e82441146bd47cc2053272689e338a 100644
--- a/packages/client/src/components/chart-tooltip.vue
+++ b/packages/client/src/components/chart-tooltip.vue
@@ -1,5 +1,5 @@
 <template>
-<MkTooltip ref="tooltip" :showing="showing" :x="x" :y="y" :max-width="340" @closed="emit('closed')">
+<MkTooltip ref="tooltip" :showing="showing" :x="x" :y="y" :max-width="340" :direction="'left'" :inner-margin="16" @closed="emit('closed')">
 	<div v-if="title" class="qpcyisrl">
 		<div class="title">{{ title }}</div>
 		<div v-for="x in series" class="series">
diff --git a/packages/client/src/components/ui/tooltip.vue b/packages/client/src/components/ui/tooltip.vue
index 1892877cc106a809c2d33991bccbb10ccff37fee..3ccd1b73169396704eae874808689d4c7b403b94 100644
--- a/packages/client/src/components/ui/tooltip.vue
+++ b/packages/client/src/components/ui/tooltip.vue
@@ -17,8 +17,12 @@ const props = withDefaults(defineProps<{
 	y?: number;
 	text?: string;
 	maxWidth?: number;
+	direction?: 'top' | 'bottom' | 'right' | 'left';
+	innerMargin?: number;
 }>(), {
 	maxWidth: 250,
+	direction: 'top',
+	innerMargin: 0,
 });
 
 const emit = defineEmits<{
@@ -34,39 +38,144 @@ const setPosition = () => {
 	const contentWidth = el.value.offsetWidth;
 	const contentHeight = el.value.offsetHeight;
 
-	let left: number;
-	let top: number;
-
 	let rect: DOMRect;
 
 	if (props.targetElement) {
 		rect = props.targetElement.getBoundingClientRect();
+	}
+
+	const calcPosWhenTop = () => {
+		let left: number;
+		let top: number;
+
+		if (props.targetElement) {
+			left = rect.left + window.pageXOffset + (props.targetElement.offsetWidth / 2);
+			top = (rect.top + window.pageYOffset - contentHeight) - props.innerMargin;
+		} else {
+			left = props.x;
+			top = (props.y - contentHeight) - props.innerMargin;
+		}
+
+		left -= (el.value.offsetWidth / 2);
+
+		if (left + contentWidth - window.pageXOffset > window.innerWidth) {
+			left = window.innerWidth - contentWidth + window.pageXOffset - 1;
+		}
+
+		return [left, top];
+	}
+
+	const calcPosWhenBottom = () => {
+		let left: number;
+		let top: number;
+
+		if (props.targetElement) {
+			left = rect.left + window.pageXOffset + (props.targetElement.offsetWidth / 2);
+			top = (rect.top + window.pageYOffset + props.targetElement.offsetHeight) + props.innerMargin;
+		} else {
+			left = props.x;
+			top = (props.y) + props.innerMargin;
+		}
+
+		left -= (el.value.offsetWidth / 2);
 
-		left = rect.left + window.pageXOffset + (props.targetElement.offsetWidth / 2);
-		top = rect.top + window.pageYOffset - contentHeight;
+		if (left + contentWidth - window.pageXOffset > window.innerWidth) {
+			left = window.innerWidth - contentWidth + window.pageXOffset - 1;
+		}
 
-		el.value.style.transformOrigin = 'center bottom';
-	} else {
-		left = props.x;
-		top = props.y - contentHeight;
+		return [left, top];
 	}
 
-	left -= (el.value.offsetWidth / 2);
+	const calcPosWhenLeft = () => {
+		let left: number;
+		let top: number;
+
+		if (props.targetElement) {
+			left = (rect.left + window.pageXOffset - contentWidth) - props.innerMargin;
+			top = rect.top + window.pageYOffset + (props.targetElement.offsetHeight / 2);
+		} else {
+			left = (props.x - contentWidth) - props.innerMargin;
+			top = props.y;
+		}
+
+		top -= (el.value.offsetHeight / 2);
+
+		if (top + contentHeight - window.pageYOffset > window.innerHeight) {
+			top = window.innerHeight - contentHeight + window.pageYOffset - 1;
+		}
 
-	if (left + contentWidth - window.pageXOffset > window.innerWidth) {
-		left = window.innerWidth - contentWidth + window.pageXOffset - 1;
+		return [left, top];
 	}
 
-	// ツールチップを上に向かって表示するスペースがなければ下に向かって出す
-	if (top - window.pageYOffset < 0) {
+	const calcPosWhenRight = () => {
+		let left: number;
+		let top: number;
+
 		if (props.targetElement) {
-			top = rect.top + window.pageYOffset + props.targetElement.offsetHeight;
-			el.value.style.transformOrigin = 'center top';
+			left = (rect.left + window.pageXOffset) + props.innerMargin;
+			top = rect.top + window.pageYOffset + (props.targetElement.offsetHeight / 2);
 		} else {
+			left = props.x + props.innerMargin;
 			top = props.y;
 		}
+
+		top -= (el.value.offsetHeight / 2);
+
+		if (top + contentHeight - window.pageYOffset > window.innerHeight) {
+			top = window.innerHeight - contentHeight + window.pageYOffset - 1;
+		}
+
+		return [left, top];
+	}
+
+	const calc = (): {
+		left: number;
+		top: number;
+		transformOrigin: string;
+	} => {
+		switch (props.direction) {
+			case 'top': {
+				const [left, top] = calcPosWhenTop();
+
+				// ツールチップを上に向かって表示するスペースがなければ下に向かって出す
+				if (top - window.pageYOffset < 0) {
+					const [left, top] = calcPosWhenBottom();
+					return { left, top, transformOrigin: 'center top' };
+				}
+
+				return { left, top, transformOrigin: 'center bottom' };
+			}
+
+			case 'bottom': {
+				const [left, top] = calcPosWhenBottom();
+				// TODO: ツールチップを下に向かって表示するスペースがなければ上に向かって出す
+				return { left, top, transformOrigin: 'center top' };
+			}
+
+			case 'left': {
+				const [left, top] = calcPosWhenLeft();
+
+				// ツールチップを左に向かって表示するスペースがなければ右に向かって出す
+				if (left - window.pageXOffset < 0) {
+					const [left, top] = calcPosWhenRight();
+					return { left, top, transformOrigin: 'left center' };
+				}
+
+				return { left, top, transformOrigin: 'right center' };
+			}
+
+			case 'right': {
+				const [left, top] = calcPosWhenRight();
+				// TODO: ツールチップを右に向かって表示するスペースがなければ左に向かって出す
+				return { left, top, transformOrigin: 'left center' };
+			}
+		}
+
+		return null as never;
 	}
 
+	const { left, top, transformOrigin } = calc();
+	el.value.style.transformOrigin = transformOrigin;
 	el.value.style.left = left + 'px';
 	el.value.style.top = top + 'px';
 };