diff --git a/packages/frontend/.storybook/charts.ts b/packages/frontend/.storybook/charts.ts
new file mode 100644
index 0000000000..5015012a82
--- /dev/null
+++ b/packages/frontend/.storybook/charts.ts
@@ -0,0 +1,48 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { DefaultBodyType, HttpResponse, HttpResponseResolver, JsonBodyType, PathParams, http } from 'msw';
+import seedrandom from 'seedrandom';
+import { action } from '@storybook/addon-actions';
+
+function getChartArray(seed: string, limit: number, option?: { accumulate?: boolean, mul?: number }): number[] {
+	const rng = seedrandom(seed);
+	const max = Math.floor(option?.mul ?? 250 * rng());
+	let accumulation = 0;
+	const array: number[] = [];
+	for (let i = 0; i < limit; i++) {
+		const num = Math.floor((max + 1) * rng());
+		if (option?.accumulate) {
+			accumulation += num;
+			array.unshift(accumulation);
+		} else {
+			array.push(num);
+		}
+	}
+	return array;
+}
+
+export function getChartResolver(fields: string[], option?: { accumulate?: boolean, mulMap?: Record<string, number> }): HttpResponseResolver<PathParams, DefaultBodyType, JsonBodyType> {
+	return ({ request }) => {
+		action(`GET ${request.url}`)();
+		const limitParam = new URL(request.url).searchParams.get('limit');
+		const limit = limitParam ? parseInt(limitParam) : 30;
+		const res = {};
+		for (const field of fields) {
+			const layers = field.split('.');
+			let current = res;
+			while (layers.length > 1) {
+				const currentKey = layers.shift()!;
+				if (current[currentKey] == null) current[currentKey] = {};
+				current = current[currentKey];
+			}
+			current[layers[0]] = getChartArray(field, limit, {
+				accumulate: option?.accumulate,
+				mul: option?.mulMap != null && field in option.mulMap ? option.mulMap[field] : undefined,
+			});
+		}
+		return HttpResponse.json(res);
+	};
+}
diff --git a/packages/frontend/src/components/MkChart.stories.impl.ts b/packages/frontend/src/components/MkChart.stories.impl.ts
index 3bae703245..1bcb9c30d8 100644
--- a/packages/frontend/src/components/MkChart.stories.impl.ts
+++ b/packages/frontend/src/components/MkChart.stories.impl.ts
@@ -6,52 +6,11 @@
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 /* eslint-disable import/no-default-export */
 import { StoryObj } from '@storybook/vue3';
-import { DefaultBodyType, HttpResponse, HttpResponseResolver, JsonBodyType, PathParams, http } from 'msw';
-import seedrandom from 'seedrandom';
-import { action } from '@storybook/addon-actions';
+import { http } from 'msw';
 import { commonHandlers } from '../../.storybook/mocks.js';
+import { getChartResolver } from '../../.storybook/charts.js';
 import MkChart from './MkChart.vue';
 
-function getChartArray(seed: string, limit: number, option?: { accumulate?: boolean, mul?: number }): number[] {
-	const rng = seedrandom(seed);
-	const max = Math.floor(option?.mul ?? 250 * rng());
-	let accumulation = 0;
-	const array: number[] = [];
-	for (let i = 0; i < limit; i++) {
-		const num = Math.floor((max + 1) * rng());
-		if (option?.accumulate) {
-			accumulation += num;
-			array.unshift(accumulation);
-		} else {
-			array.push(num);
-		}
-	}
-	return array;
-}
-
-export function getChartResolver(fields: string[], option?: { accumulate?: boolean, mulMap?: Record<string, number> }): HttpResponseResolver<PathParams, DefaultBodyType, JsonBodyType> {
-	return ({ request }) => {
-		action(`GET ${request.url}`)();
-		const limitParam = new URL(request.url).searchParams.get('limit');
-		const limit = limitParam ? parseInt(limitParam) : 30;
-		const res = {};
-		for (const field of fields) {
-			const layers = field.split('.');
-			let current = res;
-			while (layers.length > 1) {
-				const currentKey = layers.shift()!;
-				if (current[currentKey] == null) current[currentKey] = {};
-				current = current[currentKey];
-			}
-			current[layers[0]] = getChartArray(field, limit, {
-				accumulate: option?.accumulate,
-				mul: option?.mulMap != null && field in option.mulMap ? option.mulMap[field] : undefined,
-			});
-		}
-		return HttpResponse.json(res);
-	};
-}
-
 const Base = {
 	render(args) {
 		return {
diff --git a/packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts b/packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts
index a069a0eb8e..9e8de9d878 100644
--- a/packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts
+++ b/packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts
@@ -8,8 +8,9 @@ import { StoryObj } from '@storybook/vue3';
 import { HttpResponse, http } from 'msw';
 import { federationInstance } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
+import { getChartResolver } from '../../.storybook/charts.js';
 import MkInstanceCardMini from './MkInstanceCardMini.vue';
-import { getChartResolver } from './MkChart.stories.impl.js';
+
 export const Default = {
 	render(args) {
 		return {