Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React from 'react';
import '../stylesheets/App.css';
import ChartShowcase from './ChartShowcase';

const App: React.FC = () => {
return (
<div className="App">
<ChartShowcase />
</div>
);
}
Expand Down
55 changes: 55 additions & 0 deletions src/components/ChartShowcase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useState } from 'react';
import Heatmap from './charts/Heatmap';
import {range, randomInt} from '../util';

const margin = {top: 2, left: 2};
const heatmapSize = {width: 10, height: 10};
const showcaseSize = {width: 50, height: 50};

const generateHeatmapData = (max: number): number[][] => {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

行数: 1-10
列数: 1-10
の範囲でランダムに配列の配列を生成。

const xNum = 1 + randomInt(9)
const yNum = 1 + randomInt(9)
return range(0, yNum).map(
(y) => range(0, xNum).map(
(x)=> randomInt(max)
)
)
}

const ChartShowcase = () => {
const [heatmapData, setHeatmapData] = useState<number[][]>([[10]]);
const handleButton = () => {
setHeatmapData(generateHeatmapData(50))
}

return (
<article className="chart-showcase">
<section className="chart-showwcase__title">
<h1>SVG Chart Showcase</h1>
</section>
<section>
<h1 className="chart-showwcase__element-title">Heatmap SVG</h1>
<div>
<button onClick={handleButton}>Generate Heatmap data</button>
<div>
{ heatmapData.map((e,i) => (<div key={i}>{ e.toString() }</div>)) }
</div>
<svg
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SVGには、viewBox 以外にも、height width viewPort などの高さや幅に関する属性を設定することができます。

viewBoxを指定することで、横幅や縦幅を SVGの親となる要素の横幅や縦幅に合わせることが出来ます。

viewBox={`0 0 ${showcaseSize.width} ${showcaseSize.height}`}
xmlns="http://www.w3.org/2000/svg"
version="1.1"
>
<Heatmap
margin={margin}
size={heatmapSize}
data={heatmapData}
max={50}
/>
</svg>
</div>
</section>
</article>
);
}

export default ChartShowcase;
73 changes: 73 additions & 0 deletions src/components/charts/Heatmap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';

type Margin = {
top: number;
left: number;
}
type Size = {
width: number;
height: number;
}

type Props = {
margin: Margin;
size: Size;
data: number[][];
max: number;
};

const calcElementSize = (data: number[][], size: Size): Size => {
if (data.length > 0 && data[0].length > 0 ) {
const width = size.width/data[0].length;
const height = size.height/data.length;
return {width, height};
}
throw new Error("data is invalid");
}

const generateColor = (n: number, max: number): [number, number, number] => {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

プロットする値が 0 に近いほど青色に近くなり、max に近いほど 赤色に近くなります。
青色と赤色のRGB表現とその差から、描画する値の色を決定しています。

const blue = [0, 103, 192];
const red = [255, 0, 0];
const diff = [red[0]-blue[0], red[1]-blue[1], red[2]-blue[2]];
const percentage = n/max
return [
diff[0]*percentage+blue[0],
diff[1]*percentage+blue[1],
diff[2]*percentage+blue[2]
]
}

const drawElementRect = (data: number[][], size: Size, max: number) => {
const elementSize = calcElementSize(data, size);
return data.map(
(nl,y) => nl.map((n,x)=> (
<rect key={`${x}-${y}`} className="charts__heatmap__rect-element"
x={x*elementSize.width}
y={y*elementSize.height}
style={
{
fill: `rgb(${generateColor(n, max).join(',')})`
}
}
width={elementSize.width}
height={elementSize.height}
>{n}</rect>
))
);
};

const Heatmap = ({margin, size, data, max}: Props) => {
return (
<g transform={`translate(${margin.left},${margin.top})`}>
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://developer.mozilla.org/ja/docs/Web/SVG/Element/g

g 要素はオブジェクトをグループ化するためのコンテナです

g要素でグループ化し、そのグループに対して、marginを設定しています。
これにより、グループ内の座標をローカル座標として扱うことが出来ます。
コンポーネント内をローカル座標計として扱うことでコンポーネント内の描画対象の座標計算を簡略することが出来ます。

<rect className="charts__heatmap__rect-element"
x={0}
y={0}
width={size.width}
height={size.height}
/>
{ drawElementRect(data, size, max) }
</g>
);
}

export default Heatmap;
9 changes: 9 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const range =
(start: number, end: number) => Array.from({length: (end - start + 1)}, (v, k) => k + start);

const randomInt = (max: number): number => Math.floor(Math.random() * Math.floor(max));

export {
range,
randomInt
}