0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

グラフライブラリNivoを使ってみた

Posted at

はじめに

グラフライブラリの選定をしているときに、Nivoというものを見つけました。

公式サイトで設定を色々試してみることができるし、その結果のコードをコピペすることもできるで「これは楽かも」と思い、試してみることにしました。

・・・すぐに後悔しました。ドキュメントらしいドキュメントがないのです! 基本的にResponsiveLineコンポーネントに複数の属性を渡して設定を行うのですが、ResponsiveLineに何を渡すことができるのか、それぞれがどのような役割を果たしているのかの説明がどう考えても不足しています。

今後ドキュメントが書かれることを期待していますが、それまでにどうしてもNivoを使ってみたいという人向けに、試してみてわかった範囲のことを残そうと思います。

(やり方がどうにもわからなかったものについては、styled-componentを使って乗り切ってしまった部分がありますし、誤りもあるかと思いまう。ご了承ください)

成果物イメージ

image.png

準備

今回はNext.jsを使ってやりたいので、適当なディレクトリでnpx create-next-app@latest --typescriptを実行してプロジェクトを作成します。

その後、必要なライブラリを入れていきます。

npx create-next-app@latest --typescript

// 必要があれば適切なディレクトリに移動

// nivoのインストール
yarn add @nivo/line

スタート画面を構成している諸々のものはいらないので消しておいて、page.tsxは以下のようにします。

import MyResponsiveLine from "@/features/MyResponsiveLine";

export default function Home() {
	return (
		<div>
			<div style={{ height: "500px" }}>
				{/* ↓これから作る */}
				<MyResponsiveLine />
			</div>
		</div>
	);
}

MyResponsiveLineコンポーネントにてNivoの設定をしていきます!

ここで注意ですが、MyResponsiveLineの親要素には必ず height を明示するようにしましょう!

Nivoの公式からコピペする

Nivoの公式ページからコードを丸ごとコピペします。これの設定を変更して、最終的な成果物作成を目指します。

今回dataは以下のものを使います。

const simpleData = [
	{
		id: "cost",
		data: [
			{ x: "Sun", y: 1000 },
			{ x: "Mon", y: 800 },
			{ x: "Tue", y: 1300 },
			{ x: "Wed", y: 1250 },
			{ x: "Thur", y: 1290 },
			{ x: "Fri", y: 1080 },
			{ x: "Sat", y: 1500 },
		],
	},
];

グラフの設定

線の種類

グラフの線の種類はcurveで変更することができます。

curveの型を見てみると

curve?:
  | 'basis'
  | 'cardinal'
  | 'catmullRom'
  | 'linear'
  | 'monotoneX'
  | 'monotoneY'
  | 'natural'
  | 'step'
  | 'stepAfter'
  | 'stepBefore'

このようになっているので、これらの値を指定することができそうです。

それぞれがどのようなものかは公式ページで確認できます。今回はcatmullRomにします。

catmullRom が何かについては以下の記事が参考になります。

グラフの色

ついでに線の色も変えておきましょう。 colors で変更することができます。

colors には”red”や”#FC60FF”などの文字列の他に、文字列の配列を渡すこともできます。複数の線を描画したい時は配列を渡すことで、すべての線の色を指定することができます。

curve="catmullRom"
colors={"red"}

余談ですが、Nivoには複数のカラーテーマが用意されており、これらを使うこともできます。

丸を消す

縦の線とグラフが交わるところに丸が表示されています。これは point で管理されています。今回 point は不要なので、point に関係するものは削除します。またenablePoint={false}を追記して、丸を使わないことを明示しておきましょう。これを設定しないと丸が残ってしまいます。

// 削除
pointSize={5}
pointColor={{ theme: "background" }}
pointBorderWidth={2}
pointBorderColor={{ from: "serieColor" }}
pointLabel="data.yFormatted"
pointLabelYOffset={-12}

// 追加
enablePoint={false}

軸の設定を変える

X軸の設定

設定はxScaleで行います。

xScale={{
  type: 'point', // カテゴリデータとしてポイントを使用
  min: 0,      // 最小値(線形スケールの場合)
  max: 10,     // 最大値(線形スケールの場合)
  stacked: false, // スタックしない
  reverse: false, // 逆順にはしない
}}

このように、軸の最大値や最小値、逆順で表示するかなどを設定することができます。

今回はxScale={{ type: "point" }}だけ指定します。この設定により、各ポイントが等間隔に配置されます。

pointの他にもlinearを指定することができます。

Y軸の設定

設定はyScaleで行います。設定の方法はxScaleと同様にオブジェクトを渡します。

今回は以下のように設定します。

yScale={{
	type: "linear",
	min: 0, // Y軸の最小値
	max: 2000, // Y軸の最大値
	stacked: false, // グラフを積み上げて表示するか
	reverse: false, // 逆順表示するか
}}

Y軸は0〜2000で固定したいので、minは0、maxは2000で固定します。データの値によってmaxを変動させたい場合は、”auto”を指定しましょう。最大値は必ず特定の桁で切り上げたいなど細かい要望がある場合は、別途そのようなhooksを実装しましょう。

積み上げも逆順もしないので、stackedreverseはどちらもfalseにします。

↓積み上げとは

axisの設定

それぞれの軸に表示する文字や説明を設定するのがaxisです。

現段階では軸の側にcountやtransportationと表示されていますが、これは不要なので消したいです。

この設定はaxisBottomaxisLeftで行います。

axisBottom={{
	tickSize: 0,
	tickPadding: 5,
	renderTick: ({ value, x, y }) => (
		<text
			x={x}
			y={y + 18}
			fill="#79858C"
			fontSize="11"
			textAnchor="middle"
		>
			{value}
		</text>
	),
}}
axisLeft={{
	tickSize: 0,
	tickPadding: 5.72,
	tickValues: [0, 500, 1000, 1500, 2000],
	renderTick: ({ value, x, y }) => (
		<text
			x={x - 35}
			y={y}
			fill="#79858C"
			fontSize="11"
			dominantBaseline="middle"
		>
			{value}
		</text>
	),
}}

tickは軸のメモリとして表示されている文字のことです。tickの色を直接変更する方法が見つからなかったので、従来のtickのSizeを0にして、renderTickで生成しています。<tetx>はsvgの書き方です。

axisLeftの方ではメモリとして表示するものを指定しています。この内容とyScaleで指定した内容が合うように注意しましょう。

countやtransportationはlegendsというもので管理されています。今回legendsは不要なのでこれに関係するものは削除します。

また、グラフの説明としてグラフ外にcountと書かれたものがありますが、これもlegendsです。ついでに削除しておきましょう。

ツールチップを変更する

デフォルトのツールチップは以下のようになっており、少しダサいです。

スクリーンショット 2024-11-03 13.42.21.png

なので独自のツールチップを設定しようと思います。以下をResponsiveLineコンポーネントに追加しましょう。

tooltip={({ point }: { point: Point }) => (
	<StyledTooltip>
		<div>
			<TooltipGeneratedText>{point.data.yFormatted}</TooltipGeneratedText>
		</div>
		{/* 下の吹き出し */}
		<StyledTooltipArrow />
	</StyledTooltip>
)}

StyledTooltipなどは以下のように定義しました。

  • スタイル

    import styled from "styled-components";
    
    const TooltipGeneratedText = styled.span`
    	font-family: Nunito;
    	font-size: 13px;
    	font-weight: 600;
    `;
    
    const StyledUnit = styled.span`
    	font-family: Nunito;
    	font-size: 11px;
    	font-weight: 600;
    	margin-left: 5px;
    `;
    
    const StyledTooltip = styled.div`
    	color: black;
    	background-color: white;
    	border: 2px solid red;
    	border-radius: 8px;
    	width: auto;
    	height: auto;
    	padding: 13px 11px 11px;
    `;
    
    const StyledTooltipArrow = styled.div`
    	position: absolute;
    	left: calc(50% - 6px);
    	transform: translateX(-50%);
    	width: 0;
    	height: 0;
    
    	// 枠線下の三角形
    	&::before {
    		content: "";
    		position: absolute;
    		bottom: -20px;
    		border-left: 6px solid transparent;
    		border-right: 6px solid transparent;
    		border-top: 9px solid red;
    	}
    
    	// 白い三角形の部分
    	&::after {
    		content: "";
    		position: absolute;
    		bottom: -16px;
    		border-left: 6px solid transparent;
    		border-right: 6px solid transparent;
    		border-top: 9px solid white;
    		z-index: 2;
    	}
    `;
    

これで以下のようなツールチップになりました。

image (1).png

グラフの背景を変える

格子をなくす

デザイン上邪魔な場合は消すこともできます。

enableGridX={false}
enableGridY={false}

どちらか一方の線だけ消すこともできます。

背景に何かを表示する(応用?)

グラフの背景に文字を表示したりすることができます。今回はグラフの背景だけ色を変更するようにしてみます。

layers={[
	"markers",
	"axes",
	"areas",
	"lines",
	"slices",
	"mesh",
	CustomLayer,
]}

layersはどのような順番でlineなどを表示するか決めることができます。ここにカスタムコンポーネントを渡すことで、独自のコンポーネントを表示させることができます。

CustomLayerは以下のように定義しました。

// グラフ部分の背景色
const CustomLayer = (props: CustomLayerProps) => {
	const { innerHeight, innerWidth } = props;
	return (
		<g transform={`translate(0, 0)`} style={{ zIndex: -10 }}>
			<rect
				x={0}
				y={0}
				width={innerWidth}
				height={innerHeight}
				fill="rgba(255, 153, 0, 0.05)"
				style={{ pointerEvents: "none" }}
			/>
		</g>
	);
};

これでグラフの背景に色がついたかと思います。

クリックしたところだけPointをつけたい

「すべてのPointを無効化したものの、クリックした部分にはPointがついてほしい」という場合も、layerにカスタムコンポーネントを渡すことで実現できます。

// グラフをクリックしたときに表示するpoint
const CustomPointLayer = (clickedPoint: Point | null) =>
	function CustomPointLayerContext() {
		if (!clickedPoint) return null;
		return (
			<g style={{ pointerEvents: "none" }}>
				<circle
					cx={clickedPoint.x}
					cy={clickedPoint.y}
					r={4}
					stroke="red"
					strokeWidth={2}
					fill="white"
				/>
			</g>
		);
	};

const MyResponsiveLine = () => {	
	const [clickedPoint, setClickedPoint] = useState<Point | null>(null);

	const handleClick = (point: Point) => {
		setClickedPoint(point);
	};
	const handleMouseLeave = () => {
		setClickedPoint(null);
	};

	return (
		
		// ... 省略
		
		layers={[
			"markers",
			"axes",
			"areas",
			"lines",
			"slices",
			"mesh",
			CustomLayer,
			CustomPointLayer(clickedPoint),
		]}
		onClick={handleClick}
		onMouseLeave={handleMouseLeave} // グラフ外の場所をクリックした時Pointを消す

終わりに

最終的なコードはこちらです。

"use client";

import styled from "styled-components";
import { ResponsiveLine, Point, CustomLayerProps } from "@nivo/line";
import { useState } from "react";

const StyledTooltip = styled.div`
	color: black;
	background-color: white;
	border: 2px solid red;
	border-radius: 8px;
	width: auto;
	height: auto;
	padding: 13px 11px 11px;
`;

const TooltipGeneratedText = styled.span`
	font-size: 13px;
	font-weight: 600;
`;

const StyledTooltipArrow = styled.div`
	position: absolute;
	left: calc(50% - 6px);
	transform: translateX(-50%);
	width: 0;
	height: 0;

	// 枠線下の三角形
	&::before {
		content: "";
		position: absolute;
		bottom: -20px;
		border-left: 6px solid transparent;
		border-right: 6px solid transparent;
		border-top: 9px solid red;
	}

	// 白い三角形の部分
	&::after {
		content: "";
		position: absolute;
		bottom: -16px;
		border-left: 6px solid transparent;
		border-right: 6px solid transparent;
		border-top: 9px solid white;
		z-index: 2;
	}
`;

// グラフ部分の背景色
const CustomLayer = (props: CustomLayerProps) => {
	const { innerHeight, innerWidth } = props;
	return (
		<g transform={`translate(0, 0)`} style={{ zIndex: -10 }}>
			<rect
				x={0}
				y={0}
				width={innerWidth}
				height={innerHeight}
				fill="rgba(255, 153, 0, 0.05)"
				style={{ pointerEvents: "none" }}
			/>
		</g>
	);
};

// グラフをクリックしたときに表示するpoint
const CustomPointLayer = (clickedPoint: Point | null) =>
	function CustomPointLayerContext() {
		if (!clickedPoint) return null;
		return (
			<g style={{ pointerEvents: "none" }}>
				<circle
					cx={clickedPoint.x}
					cy={clickedPoint.y}
					r={4}
					stroke="red"
					strokeWidth={2}
					fill="white"
				/>
			</g>
		);
	};

const simpleData = [
	{
		id: "cost",
		data: [
			{ x: "Sun", y: 1000 },
			{ x: "Mon", y: 800 },
			{ x: "Tue", y: 1300 },
			{ x: "Wed", y: 1250 },
			{ x: "Thur", y: 1290 },
			{ x: "Fri", y: 1080 },
			{ x: "Sat", y: 1500 },
		],
	},
];

const MyResponsiveLine = () => {
	const [clickedPoint, setClickedPoint] = useState<Point | null>(null);

	const handleClick = (point: Point) => {
		setClickedPoint(point);
	};
	const handleMouseLeave = () => {
		setClickedPoint(null);
	};

	return (
		<ResponsiveLine
			data={simpleData} // グラフ表示したいデータを渡す
			margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
			useMesh={true} // これをtrueにしないとクリックできなくなり、ツールチップも無効になる
			curve="catmullRom"
			colors={"red"}
			enablePoints={false}
			xScale={{ type: "point" }}
			yScale={{
				type: "linear",
				min: 0,
				max: 2000,
				stacked: false,
				reverse: false,
			}}
			axisBottom={{
				tickSize: 0,
				tickPadding: 5,
				renderTick: ({ value, x, y }) => (
					<text
						x={x}
						y={y + 18}
						fill="#79858C"
						fontSize="11"
						textAnchor="middle"
					>
						{value}
					</text>
				),
			}}
			axisLeft={{
				tickSize: 0,
				tickPadding: 5.72,
				tickValues: [0, 500, 1000, 1500, 2000],
				renderTick: ({ value, x, y }) => (
					<text
						x={x - 35}
						y={y}
						fill="#79858C"
						fontSize="11"
						dominantBaseline="middle"
					>
						{value}
					</text>
				),
			}}
			tooltip={({ point }: { point: Point }) => (
				<StyledTooltip>
					<div>
						<TooltipGeneratedText>
							{point.data.yFormatted}</TooltipGeneratedText>
					</div>
					{/* 下の吹き出し */}
					<StyledTooltipArrow />
				</StyledTooltip>
			)}
			enableGridX={false}
			enableGridY={false}
			layers={[
				"markers",
				"axes",
				"areas",
				"lines",
				"slices",
				"mesh",
				CustomLayer,
				CustomPointLayer(clickedPoint),
			]}
			onClick={handleClick}
			onMouseLeave={handleMouseLeave}
		/>
	);
};

export default MyResponsiveLine;

ここまで書きましたが、ドキュメントが整備されるまでNivoを使わない方がいい気がします。設定方法を探すのがとても大変でした。

公式サイトのコードをコピペして事足りるようなものであれば、Nivoを検討してもいいかもしれませんね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?