Bladeren bron

Add heatmap to Admin panel

Tatiana Inama 6 jaren geleden
bovenliggende
commit
eb188f7611
5 gewijzigde bestanden met toevoegingen van 170 en 138 verwijderingen
  1. 4 7
      src/Admin.css
  2. 8 2
      src/Admin.tsx
  3. 2 1
      src/Heatmap.css
  4. 154 126
      src/Heatmap.tsx
  5. 2 2
      src/Router.tsx

+ 4 - 7
src/Admin.css

@@ -7,16 +7,13 @@
   background-color: white;
 }
 
-#vom-results {
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  gap: 1rem 1rem;
-  grid-template-areas: "vom-admin-canvas vom-results-panel"
-  "vom-results-table vom-results-table";
+.vom-canvii {
+  display: flex;
+  align-items: flex-start;
 }
 
 #vom-admin-canvas {
-  max-height: 90vh;
+  width: 50%!important;
   justify-self: center;
   grid-area: vom-admin-canvas;
   border: 1px solid rgba(0, 0, 0, 0.1);

+ 8 - 2
src/Admin.tsx

@@ -5,6 +5,7 @@ import { without, append, isNil, identity, includes, is } from 'ramda';
 import Axios from 'axios';
 import VALUES, { AgeVal, GenderVal, NonNativeVal, Filters, FilterVal, FilterStatus, EducationVal } from './services';
 import FileDownload from 'js-file-download';
+import Heatmap from './Heatmap';
 import './Admin.css';
 
 const BACKEND = process.env.REACT_APP_BACKEND || '/backend/';
@@ -143,7 +144,7 @@ class Admin extends React.Component<{}, AdminState> {
     }).then(response => {
       const _data = response.data.map((result, index) => {
         let group = new Group();
-        return {
+        const item = {
           ...result,
           canvas: result.canvas.map((item, i) => {
             const _path = mkPath(item.path, index, canvas.view.size.width / result.canvasSize.width);
@@ -156,6 +157,8 @@ class Admin extends React.Component<{}, AdminState> {
           }),
           group: group,
         };
+        canvas.project.activeLayer.addChild(group);
+        return item;
       })
       this.setState({
         canvas,
@@ -248,8 +251,11 @@ class Admin extends React.Component<{}, AdminState> {
               applyFilters={this.applyFilters}
             ></FilterPanel>
           </div>
-          <div id="vom-results">
+          <div className="vom-canvii">
             <canvas id="vom-admin-canvas"></canvas>
+            <Heatmap data={this.state.data}></Heatmap>
+          </div>
+          <div id="vom-results">
             <div id="vom-results-panel">
               <div id="vom-focused-result" className="mt-3">
                 {

+ 2 - 1
src/Heatmap.css

@@ -1,5 +1,6 @@
+
 .vom-heatmap {
-  padding-top: 1rem;
+  width: 50%;
 }
 
 .vom-heatmap #vom-heatmap-canvas {

+ 154 - 126
src/Heatmap.tsx

@@ -1,5 +1,5 @@
 import React, { Component } from 'react';
-import { Path, PaperScope, Point, Size, Rectangle, Color, Layer, Raster, PointText, Group } from 'paper';
+import { Path, PaperScope, Point, Size, Rectangle, Color, Layer, Raster } from 'paper';
 import { Container, CustomInput, FormGroup, Row, Col, Button } from 'reactstrap';
 import FileSaver from 'file-saver';
 import map from './merseyside-nobg-white.png';
@@ -37,6 +37,57 @@ enum HeatmapType {
   Trustworthiness = 'trustworthiness'
 }
 
+type PersonalInformation = {
+  age: string,
+  gender: string,
+  genderCustom: string,
+  birthPlace: string,
+  currentPlace: string,
+  levelEducation: string[],
+  nonNative: string,
+}
+
+type FormPath = {
+  name: string,
+  soundExample: string,
+  associations: string[],
+  correctness: number,
+  friendliness: number,
+  pleasantness: number,
+  trustworthiness: number
+}
+
+type OriginalCanvasData = {
+  form: FormPath,
+  path: string,
+}
+
+type Result = {
+  personalInformation: PersonalInformation,
+  canvas: {
+    form: FormPath,
+    path: paper.Path
+  }[],
+  email: string,
+  id: number,
+}
+
+type HeatmapProps = {
+  data: Result[],
+}
+
+type HeatmapState = {
+  data: HeatmapData[],
+  canvas?: paper.PaperScope,
+  grid: Grid,
+  heatmapLayer: paper.Layer | undefined,
+  mapLayer: paper.Layer | undefined,
+  gridLayer: paper.Layer | undefined,
+  areaAmount: paper.TextItem | undefined,
+  heatmapType: HeatmapType,
+}
+
+
 class Heatmap extends Component<HeatmapProps, HeatmapState> {
   
   constructor(props: HeatmapProps) {
@@ -46,7 +97,9 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
       data: [],
       canvas: undefined,
       grid: [],
+      heatmapLayer: undefined,
       mapLayer: undefined,
+      gridLayer: undefined,
       areaAmount: undefined,
       heatmapType: HeatmapType.Amount,
     }
@@ -63,7 +116,14 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
     return _path;
   }
   
-  drawMapData = (data: Result[], canvasWidth: number) => {
+  addshape = (path: paper.Path, data: HeatmapData, canvas: paper.PaperScope) => {
+    const _path = new canvas.Path(path.segments);
+    _path.data = data;
+    canvas.project.layers[0].addChild(_path);
+  }
+
+  drawMapData = (data: Result[], canvas: paper.PaperScope) => {
+    canvas.project.layers[0]?.removeChildren();
     const _data = data.reduce<Array<HeatmapData>>((newData, result) => {
       return [
         ...newData,
@@ -75,7 +135,7 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
             pleasantness: shape.form.pleasantness,
             trustworthiness: shape.form.trustworthiness,
           }
-          this.drawShape(shape.path, canvasWidth / result.canvasSize.width, data);
+          this.addshape(shape.path, data, canvas);
           return data as HeatmapData;
         })
       ]
@@ -102,15 +162,21 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
     return chunks;
   }
   
-  mkGrid = (canvas: paper.PaperScope): Grid=> {
+  mkGrid = (canvas: paper.PaperScope, itemsLayer: paper.Layer): { grid: Grid, layer: paper.Layer } => {
     const bigChunkSizeX = 4, bigChunkSizeY = 5;
-    const mainLayer = canvas.project.activeLayer;
-    return this.mkChunk(bigChunkSizeX*X_SEGMENTS, Y_SEGMENTS*bigChunkSizeY, canvas.project.view.bounds, mainLayer).map(({rectangle, items}) => {
+    let layer = new canvas.Layer({ name: 'heatmap' });
+    const grid = this.mkChunk(bigChunkSizeX*X_SEGMENTS, Y_SEGMENTS*bigChunkSizeY, canvas.project.view.bounds, itemsLayer).map(({rectangle, items}) => {
+      let area = new canvas.Path.Rectangle(rectangle);
+      layer.addChild(area);
       return {
-        area: new Path.Rectangle(rectangle),
+        area,
         items
       }
-    })
+    });
+    return {
+      grid,
+      layer
+    }
     // this.mkChunk(bigChunkSizeX, bigChunkSizeY, canvas.project.view.bounds, mainLayer, true).forEach((chunk, i) => {
     //   const x = new Path.Rectangle(chunk.rectangle);
     //   console.log(x.bounds)
@@ -137,8 +203,8 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
         return item.data[key] ? tot + item.data[key] : tot;
       }, 0);
       return total !== 0 ? 
-      new Color({ hue: RANGE(total / items.length), saturation: 1, brightness: items.length/MAX}) : 
-      new Color('black')
+        new Color({ hue: RANGE(total / items.length), saturation: 1, brightness: items.length/MAX}) : 
+        new Color('black')
     }
   }
   
@@ -178,19 +244,37 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
     const canvas = new PaperScope();
     canvas.setup('vom-heatmap-canvas');
     canvas.view.viewSize.height = canvas.view.size.width * 1.25;
-    const data = this.drawMapData(this.props.data, canvas.view.size.width);
-    const grid = this.mkGrid(canvas);
+    const data = this.drawMapData(this.props.data, canvas);
+    const { grid, layer } = this.mkGrid(canvas, canvas.project.activeLayer);
     this.changeHeatmapColor(HeatmapType.Friendliness, grid);
     
     this.setState({
       data,
       canvas,
       grid,
+      heatmapLayer: layer,
       heatmapType: HeatmapType.Friendliness,
       mapLayer: this.showMap(canvas)
     })
   }
 
+  componentDidUpdate = (prev: HeatmapProps) => {
+    if (prev.data.length !== this.props.data.length) {
+      const canvas = this.state.canvas!;
+      const data = this.drawMapData(this.props.data, canvas);
+      this.state.heatmapLayer?.remove();
+      const { grid, layer } = this.mkGrid(canvas, canvas.project.layers[0]);
+      this.changeHeatmapColor(HeatmapType.Friendliness, grid);
+      this.showMap(canvas);
+      this.setState({
+        grid,
+        heatmapLayer: layer,
+        data,
+        heatmapType: HeatmapType.Friendliness,
+      })
+    }
+  }
+
   handleChangeColor = (e: React.ChangeEvent<HTMLInputElement>) => {
     const heatmapType = e.currentTarget.value as HeatmapType;
     this.changeHeatmapColor(heatmapType, this.state.grid);
@@ -200,122 +284,66 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
   }
   
   render = () => (
-    <Container className="vom-heatmap">
-      <Row>
-        <Col xs="12" md="8">
-          <div className="vom-heatmap-map">
-            {
-              this.state.heatmapType !== HeatmapType.Amount ? (
-                <div id="vom-heatmap-range">
-                  <div>1</div>
-                  <div>2</div>
-                  <div>3</div>
-                  <div>4</div>
-                  <div>5</div>
-                </div>
-              ) : (
-                <div id="vom-heatmap-amount-range">
-                  <div>1</div>
-                  <div>200</div>
-                </div>
-              )
-            }
-            <canvas id="vom-heatmap-canvas"></canvas>
+    <div className="vom-heatmap">
+      <div className="vom-heatmap-map">
+        <canvas id="vom-heatmap-canvas"></canvas>
+        {
+          this.state.heatmapType !== HeatmapType.Amount ? (
+            <div id="vom-heatmap-range">
+              <div>1</div>
+              <div>2</div>
+              <div>3</div>
+              <div>4</div>
+              <div>5</div>
+            </div>
+          ) : (
+            <div id="vom-heatmap-amount-range">
+              <div>1</div>
+              <div>200</div>
+            </div>
+          )
+        }
+      </div>
+      <div className="vom-heatmap-actions">
+        <FormGroup>
+          <Button size="sm" onClick={() => {this.saveAsImage()} }>download current map</Button>
+          <h4>Heatmap type</h4> 
+          <div>
+            <CustomInput type="radio" id="heatmap-by-amount" name="by-amount" 
+            label="by amount of responses"
+            value={HeatmapType.Amount}
+            checked={this.state.heatmapType === HeatmapType.Amount}
+            onChange={ this.handleChangeColor }
+            />
+            <CustomInput type="radio" id="heatmap-by-friendliness" name="by-friendliness"
+            label="by friendliness"
+            value={HeatmapType.Friendliness}
+            checked={this.state.heatmapType === HeatmapType.Friendliness}
+            onChange={ this.handleChangeColor }
+            />
+            <CustomInput type="radio" id="heatmap-by-correctness" name="by-correctness"
+            label="by correctness"
+            value={HeatmapType.Correctness}
+            checked={this.state.heatmapType === HeatmapType.Correctness}
+            onChange={ this.handleChangeColor }
+            />
+            <CustomInput type="radio" id="heatmap-by-pleasantness" name="by-pleasantness"
+            label="by pleasantness"
+            value={HeatmapType.Pleasantness}
+            checked={this.state.heatmapType === HeatmapType.Pleasantness}
+            onChange={ this.handleChangeColor }
+            />
+            <CustomInput type="radio" id="heatmap-by-trustworthiness" name="by-trustworthiness"
+            label="by trustworthiness"
+            value={HeatmapType.Trustworthiness}
+            checked={this.state.heatmapType === HeatmapType.Trustworthiness}
+            onChange={ this.handleChangeColor }
+            />
           </div>
-        </Col>
-        <Col>
-          <div className="vom-heatmap-actions">
-            <FormGroup>
-              <Button size="sm" onClick={() => {this.saveAsImage()} }>download current map</Button>
-              <h4>Heatmap type</h4> 
-              <div>
-                <CustomInput type="radio" id="heatmap-by-amount" name="by-amount" 
-                label="by amount of responses"
-                value={HeatmapType.Amount}
-                checked={this.state.heatmapType === HeatmapType.Amount}
-                onChange={ this.handleChangeColor }
-                />
-                <CustomInput type="radio" id="heatmap-by-friendliness" name="by-friendliness"
-                label="by friendliness"
-                value={HeatmapType.Friendliness}
-                checked={this.state.heatmapType === HeatmapType.Friendliness}
-                onChange={ this.handleChangeColor }
-                />
-                <CustomInput type="radio" id="heatmap-by-correctness" name="by-correctness"
-                label="by correctness"
-                value={HeatmapType.Correctness}
-                checked={this.state.heatmapType === HeatmapType.Correctness}
-                onChange={ this.handleChangeColor }
-                />
-                <CustomInput type="radio" id="heatmap-by-pleasantness" name="by-pleasantness"
-                label="by pleasantness"
-                value={HeatmapType.Pleasantness}
-                checked={this.state.heatmapType === HeatmapType.Pleasantness}
-                onChange={ this.handleChangeColor }
-                />
-                <CustomInput type="radio" id="heatmap-by-trustworthiness" name="by-trustworthiness"
-                label="by trustworthiness"
-                value={HeatmapType.Trustworthiness}
-                checked={this.state.heatmapType === HeatmapType.Trustworthiness}
-                onChange={ this.handleChangeColor }
-                />
-              </div>
-            </FormGroup>
-          </div>
-        </Col>
-      </Row>
-    </Container>
+        </FormGroup>
+      </div>
+    </div>
   )
 }
       
-type PersonalInformation = {
-  age: string,
-  gender: string,
-  genderCustom: string,
-  birthPlace: string,
-  currentPlace: string,
-  levelEducation: string[],
-  nonNative: string,
-}
-
-type FormPath = {
-  name: string,
-  soundExample: string,
-  associations: string[],
-  correctness: number,
-  friendliness: number,
-  pleasantness: number,
-  trustworthiness: number
-}
-
-type OriginalCanvasData = {
-  form: FormPath,
-  path: string,
-}
-
-type Result = {
-  personalInformation: PersonalInformation,
-  canvas: OriginalCanvasData[],
-  canvasSize: {
-    width: number,
-    height: number
-  },
-  email: string,
-  id: number,
-}
-
-type HeatmapProps = {
-  data: Result[],
-}
-
-type HeatmapState = {
-  data: HeatmapData[],
-  canvas?: paper.PaperScope,
-  grid: Grid,
-  mapLayer: paper.Layer | undefined,
-  areaAmount: paper.TextItem | undefined,
-  heatmapType: HeatmapType,
-}
-
-
 export default Heatmap;

+ 2 - 2
src/Router.tsx

@@ -21,9 +21,9 @@ export const AppRouting = () => {
             <ClosureNotice></ClosureNotice>
           </Route>
           <Route path="/admin/heatmap">
-            <Heatmap
+            {/* <Heatmap
               data={results}
-            ></Heatmap>
+            ></Heatmap> */}
           </Route>
           <Route path="/admin">
             <Admin></Admin>