|
|
@@ -1,22 +1,34 @@
|
|
|
import React, { Component } from 'react';
|
|
|
-import { Path, PaperScope, Point, Size, Rectangle, PointText, Color } from 'paper';
|
|
|
+import { Path, PaperScope, Point, Size, Rectangle, Color, PointText } from 'paper';
|
|
|
|
|
|
const CANVAS_STYLE = {
|
|
|
width: '50%',
|
|
|
border: '2px solid black'
|
|
|
};
|
|
|
|
|
|
-const X_SEGMENTS = 25;
|
|
|
+const X_SEGMENTS = 30;
|
|
|
const Y_SEGMENTS = X_SEGMENTS * 1.25;
|
|
|
+const MAX = 200;
|
|
|
+const output_start = 0,
|
|
|
+ output_end = 240,
|
|
|
+ input_start = 1,
|
|
|
+ input_end = 5;
|
|
|
+const RANGE = (n: number) => output_start + ((output_end - output_start) / (input_end - input_start)) * (n - input_start);
|
|
|
+
|
|
|
+
|
|
|
+type Grid = Array<Array<{
|
|
|
+ area: paper.Path.Rectangle,
|
|
|
+ items: Array<paper.Item>
|
|
|
+}>>
|
|
|
|
|
|
type HeatmapData = {
|
|
|
id: [number, number],
|
|
|
- shape: paper.Path,
|
|
|
correctness: number,
|
|
|
friendliness: number,
|
|
|
pleasantness: number,
|
|
|
trustworthiness: number
|
|
|
}
|
|
|
+
|
|
|
class Heatmap extends Component<HeatmapProps, HeatmapState> {
|
|
|
|
|
|
constructor(props: HeatmapProps) {
|
|
|
@@ -25,11 +37,11 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
|
|
|
this.state = {
|
|
|
data: [],
|
|
|
canvas: undefined,
|
|
|
- grid: []
|
|
|
+ grid: [],
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- drawShape = (path: string, scale: number, data: any) => {
|
|
|
+ drawShape = (path: string, scale: number, data: HeatmapData) => {
|
|
|
const _path = new Path();
|
|
|
_path.importJSON(path);
|
|
|
_path.scale(scale, new Point(0, 0));
|
|
|
@@ -39,75 +51,111 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
|
|
|
return _path;
|
|
|
}
|
|
|
|
|
|
- mkGrid = (canvas: paper.PaperScope) => {
|
|
|
- const width = canvas.view.size.width,
|
|
|
- height = canvas.view.size.height;
|
|
|
- const size = new Size(width/X_SEGMENTS, height/Y_SEGMENTS);
|
|
|
-
|
|
|
- const data = this.props.data.reduce<Array<HeatmapData>>((newData, result) => {
|
|
|
+ drawMapData = (data: Result[], canvasWidth: number) => {
|
|
|
+ const _data = data.reduce<Array<HeatmapData>>((newData, result) => {
|
|
|
return [
|
|
|
...newData,
|
|
|
...result.canvas.map((shape, shapeId) => {
|
|
|
- let data = {
|
|
|
+ let data: HeatmapData = {
|
|
|
id: [result.id, shapeId],
|
|
|
correctness: shape.form.correctness,
|
|
|
friendliness: shape.form.friendliness,
|
|
|
pleasantness: shape.form.pleasantness,
|
|
|
trustworthiness: shape.form.trustworthiness,
|
|
|
- shape: {},
|
|
|
}
|
|
|
- data.shape = this.drawShape(shape.path, width / result.canvasSize.width, data);
|
|
|
+ this.drawShape(shape.path, canvasWidth / result.canvasSize.width, data);
|
|
|
return data as HeatmapData;
|
|
|
})
|
|
|
]
|
|
|
}, [])
|
|
|
+ return _data;
|
|
|
+ }
|
|
|
|
|
|
+ mkGrid = (canvas: paper.PaperScope): Grid=> {
|
|
|
+ const width = canvas.view.size.width,
|
|
|
+ height = canvas.view.size.height;
|
|
|
+ const gridSectionSize = new Size(width/X_SEGMENTS, height/Y_SEGMENTS);
|
|
|
+ let grid: Grid = [];
|
|
|
|
|
|
- let grid: Array<Array<paper.Path.Rectangle>> = [];
|
|
|
for(let row = 0; row < Y_SEGMENTS; row ++) {
|
|
|
grid[row] = [];
|
|
|
+
|
|
|
for(let column = 0; column < X_SEGMENTS; column ++) {
|
|
|
let rect = new Rectangle({
|
|
|
- point: [column * size.width, row * size.height],
|
|
|
- size: size,
|
|
|
+ point: [column * gridSectionSize.width, row * gridSectionSize.height],
|
|
|
+ size: gridSectionSize,
|
|
|
});
|
|
|
|
|
|
- let items = canvas.project.activeLayer.getItems({
|
|
|
- overlapping: rect
|
|
|
- })
|
|
|
- new Path.Rectangle({
|
|
|
- rectangle: rect,
|
|
|
- fillColor: new Color(`rgb(${items.length}, ${items.length}, ${items.length})`)
|
|
|
- })
|
|
|
- new PointText({
|
|
|
- point: rect.leftCenter,
|
|
|
- content: items.length,
|
|
|
- fillColor: items.length < 50 ? 'white' : 'black',
|
|
|
- fontSize: 10
|
|
|
- })
|
|
|
- // grid[row][column] = new Path.Rectangle({
|
|
|
- // point: [column * size.width, row * size.height],
|
|
|
- // size: size,
|
|
|
- // });
|
|
|
+ grid[row][column] = {
|
|
|
+ area: new Path.Rectangle(rect),
|
|
|
+ items: canvas.project.activeLayer.getItems({
|
|
|
+ overlapping: rect
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- this.setState({ grid, data })
|
|
|
+ return grid;
|
|
|
}
|
|
|
|
|
|
+ heatmapByAmount = (grid: Grid) => {
|
|
|
+ let xs: number[] = [];
|
|
|
+ grid.forEach(section => {
|
|
|
+ section.forEach(({ area, items }) => {
|
|
|
+ xs = [
|
|
|
+ ...xs,
|
|
|
+ items.length
|
|
|
+ ];
|
|
|
+ area.fillColor = new Color(`rgb(${items.length}, ${items.length}, ${items.length})`)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
+ heatmapBy = (key: 'correctness' | 'friendliness' | 'pleasantness' | 'trustworthiness', grid: Grid) => {
|
|
|
+ grid.forEach(section => {
|
|
|
+ section.forEach(({area, items}) => {
|
|
|
+ const total = items.reduce<number>((tot, item) => {
|
|
|
+ return tot + (item.data[key] || 0);
|
|
|
+ }, 0);
|
|
|
+ if (total !== 0) {
|
|
|
+ area.fillColor = new Color({ hue: RANGE(total / items.length), saturation: 1, brightness: items.length/MAX})
|
|
|
+ new PointText({
|
|
|
+ point: area.bounds.leftCenter,
|
|
|
+ content: (total / items.length).toFixed(1)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
componentDidMount = () => {
|
|
|
const canvas = new PaperScope();
|
|
|
canvas.setup('vom-heatmap-canvas');
|
|
|
canvas.view.viewSize.height = canvas.view.size.width * 1.25;
|
|
|
- this.mkGrid(canvas);
|
|
|
+ const data = this.drawMapData(this.props.data, canvas.view.size.width);
|
|
|
+ const grid = this.mkGrid(canvas);
|
|
|
this.setState({
|
|
|
+ data,
|
|
|
canvas,
|
|
|
+ grid,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
render = () => (
|
|
|
- <div className="vom-heatmap" style={{display: 'flex', justifyContent: 'center'}}>
|
|
|
+ <div className="vom-heatmap" style={{display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center'}}>
|
|
|
+ <div>
|
|
|
+ <button onClick={() => { this.heatmapByAmount(this.state.grid)}}>by shape amount</button>
|
|
|
+ <button onClick={() => { this.heatmapBy('friendliness', this.state.grid)}}>by friendliness</button>
|
|
|
+ <button onClick={() => { this.heatmapBy('correctness', this.state.grid)}}>by correct</button>
|
|
|
+ <button onClick={() => { this.heatmapBy('pleasantness', this.state.grid)}}>by pleasant</button>
|
|
|
+ <button onClick={() => { this.heatmapBy('trustworthiness', this.state.grid)}}>by trustworthiness</button>
|
|
|
+ </div>
|
|
|
+ <div id="vom-heatmap-range">
|
|
|
+ <div>1</div>
|
|
|
+ <div>2</div>
|
|
|
+ <div>3</div>
|
|
|
+ <div>4</div>
|
|
|
+ <div>5</div>
|
|
|
+ </div>
|
|
|
<canvas id="vom-heatmap-canvas" style={CANVAS_STYLE}></canvas>
|
|
|
</div>
|
|
|
)
|
|
|
@@ -156,7 +204,7 @@ type HeatmapProps = {
|
|
|
type HeatmapState = {
|
|
|
data: HeatmapData[],
|
|
|
canvas?: paper.PaperScope,
|
|
|
- grid: Array<Array<paper.Path.Rectangle>>
|
|
|
+ grid: Grid,
|
|
|
}
|
|
|
|
|
|
|