Bläddra i källkod

Add categorization heatmap

Tatiana Inama 6 år sedan
förälder
incheckning
94aec802e5
2 ändrade filer med 125 tillägg och 17 borttagningar
  1. 12 1
      src/Heatmap.css
  2. 113 16
      src/Heatmap.tsx

+ 12 - 1
src/Heatmap.css

@@ -1,6 +1,12 @@
 
 
 .vom-heatmap {
 .vom-heatmap {
-  width: 50%;
+  width: 100%;
+  display: flex;
+  padding: 1rem;
+}
+
+.vom-heatmap-map {
+  width: 30%;
 }
 }
 
 
 .vom-heatmap #vom-heatmap-canvas {
 .vom-heatmap #vom-heatmap-canvas {
@@ -15,6 +21,7 @@
   justify-content: space-between;
   justify-content: space-between;
   font-weight: bold;
   font-weight: bold;
   border: 2px solid black;
   border: 2px solid black;
+  margin-bottom: 0.5rem;
 }
 }
 
 
 #vom-heatmap-range {
 #vom-heatmap-range {
@@ -25,4 +32,8 @@
 #vom-heatmap-amount-range {
 #vom-heatmap-amount-range {
   background: rgb(0,0,0);
   background: rgb(0,0,0);
   background: linear-gradient(to right, #000000 1%,#ffffff 100%);
   background: linear-gradient(to right, #000000 1%,#ffffff 100%);
+}
+
+.vom-heatmap-actions {
+  padding: 0 1rem;
 }
 }

+ 113 - 16
src/Heatmap.tsx

@@ -11,10 +11,11 @@ const X_SEGMENTS = 12;
 const Y_SEGMENTS = X_SEGMENTS * 1.25;
 const Y_SEGMENTS = X_SEGMENTS * 1.25;
 const MAX = 200;
 const MAX = 200;
 const output_start = 0,
 const output_start = 0,
-output_end = 240,
-input_start = 1,
-input_end = 5;
+      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);
 const RANGE = (n: number) => output_start + ((output_end - output_start) / (input_end - input_start)) * (n - input_start);
+const OPACITY_RANGE = (n: number, inputEnd: number) => 0 + ((255 - 0) / (inputEnd)) * (n);
 
 
 const BACKEND = process.env.REACT_APP_BACKEND || '/backend/';
 const BACKEND = process.env.REACT_APP_BACKEND || '/backend/';
 
 
@@ -29,8 +30,8 @@ type HeatmapData = {
   friendliness: number,
   friendliness: number,
   pleasantness: number,
   pleasantness: number,
   trustworthiness: number,
   trustworthiness: number,
-  firstCategory: number,
-  secondCategory?:number
+  firstCategory: CategoryType,
+  secondCategory?: CategoryType
 }
 }
 
 
 enum HeatmapType {
 enum HeatmapType {
@@ -41,6 +42,30 @@ enum HeatmapType {
   Trustworthiness = 'trustworthiness'
   Trustworthiness = 'trustworthiness'
 }
 }
 
 
+enum CategoryType {
+  A,
+  B,
+  C,
+  D,
+  E,
+  F,
+  G,
+  H,
+  I
+}
+
+const CategoryTypeList: CategoryType[] = [
+  CategoryType.A,
+  CategoryType.B,
+  CategoryType.C,
+  CategoryType.D,
+  CategoryType.E,
+  CategoryType.F,
+  CategoryType.G,
+  CategoryType.H,
+  CategoryType.I
+];
+
 type PersonalInformation = {
 type PersonalInformation = {
   age: string,
   age: string,
   gender: string,
   gender: string,
@@ -90,6 +115,18 @@ type OriginalData = {
 type HeatmapProps = {
 type HeatmapProps = {
 }
 }
 
 
+type CategoryAmount = {
+  [CategoryType.A]: number,
+  [CategoryType.B]: number,
+  [CategoryType.C]: number,
+  [CategoryType.D]: number,
+  [CategoryType.E]: number,
+  [CategoryType.F]: number,
+  [CategoryType.G]: number,
+  [CategoryType.H]: number,
+  [CategoryType.I]: number
+}
+
 type HeatmapState = {
 type HeatmapState = {
   data: HeatmapData[],
   data: HeatmapData[],
   canvas?: paper.PaperScope,
   canvas?: paper.PaperScope,
@@ -97,10 +134,17 @@ type HeatmapState = {
   heatmapLayer: paper.Layer | undefined,
   heatmapLayer: paper.Layer | undefined,
   mapLayer: paper.Layer | undefined,
   mapLayer: paper.Layer | undefined,
   gridLayer: paper.Layer | undefined,
   gridLayer: paper.Layer | undefined,
-  areaAmount: paper.TextItem | undefined,
-  heatmapType: HeatmapType,
+  heatmapType: HeatmapType | CategoryType,
+  categoryAmount: CategoryAmount
 }
 }
 
 
+const CAT_AMOUNT = CategoryTypeList.reduce<CategoryAmount>((catAmount, cat) => {
+  return {
+    ...catAmount,
+    [cat]: 0
+  }
+  //@ts-ignore
+}, {});
 
 
 class Heatmap extends Component<HeatmapProps, HeatmapState> {
 class Heatmap extends Component<HeatmapProps, HeatmapState> {
   
   
@@ -114,8 +158,8 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
       heatmapLayer: undefined,
       heatmapLayer: undefined,
       mapLayer: undefined,
       mapLayer: undefined,
       gridLayer: undefined,
       gridLayer: undefined,
-      areaAmount: undefined,
       heatmapType: HeatmapType.Amount,
       heatmapType: HeatmapType.Amount,
+      categoryAmount: {...CAT_AMOUNT}
     }
     }
   }
   }
   
   
@@ -137,6 +181,7 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
   }
   }
 
 
   drawMapData = (data: OriginalData[], canvas: paper.PaperScope) => {
   drawMapData = (data: OriginalData[], canvas: paper.PaperScope) => {
+    let categoriesCounter = { ...CAT_AMOUNT };
     const _data = data.reduce<Array<HeatmapData>>((newData, result) => {
     const _data = data.reduce<Array<HeatmapData>>((newData, result) => {
       return [
       return [
         ...newData,
         ...newData,
@@ -150,12 +195,19 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
             firstCategory: shape.firstCategory,
             firstCategory: shape.firstCategory,
             secondCategory: shape.secondCategory === '' ? undefined : shape.secondCategory
             secondCategory: shape.secondCategory === '' ? undefined : shape.secondCategory
           }
           }
+          categoriesCounter[data.firstCategory] = categoriesCounter[data.firstCategory] + 1;
+          if (data.secondCategory) {
+            categoriesCounter[data.secondCategory] = categoriesCounter[data.secondCategory]
+          }
           this.drawShape(shape.path, canvas.view.size.width / result.canvasSize.width, data)
           this.drawShape(shape.path, canvas.view.size.width / result.canvasSize.width, data)
           return data as HeatmapData;
           return data as HeatmapData;
         })
         })
       ]
       ]
     }, [])
     }, [])
-    return _data;
+    return {
+      data: _data,
+      categoryAmount: categoriesCounter
+    };
   }
   }
   
   
   mkChunk = (xCount: number, yCount: number, parent: paper.Rectangle, group: paper.Group): {rectangle: paper.Rectangle, items: paper.Item[]}[] => {
   mkChunk = (xCount: number, yCount: number, parent: paper.Rectangle, group: paper.Group): {rectangle: paper.Rectangle, items: paper.Item[]}[] => {
@@ -223,6 +275,7 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
     }
     }
   }
   }
   
   
+
   changeHeatmapColor = (type: HeatmapType, grid: Grid) => {
   changeHeatmapColor = (type: HeatmapType, grid: Grid) => {
     grid.forEach(({ area, items }) => {
     grid.forEach(({ area, items }) => {
       const color = this.mkFillColor(type, items);
       const color = this.mkFillColor(type, items);
@@ -264,17 +317,17 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
         'X-Token': 'secret-potato',
         'X-Token': 'secret-potato',
       }
       }
     }).then(response => {
     }).then(response => {
-      const data = this.drawMapData(response.data, canvas);
+      const { data, categoryAmount } = this.drawMapData(response.data, canvas);
       const { grid, layer } = this.mkGrid(canvas, canvas.project.activeLayer);
       const { grid, layer } = this.mkGrid(canvas, canvas.project.activeLayer);
       this.changeHeatmapColor(HeatmapType.Friendliness, grid);
       this.changeHeatmapColor(HeatmapType.Friendliness, grid);
-      
       this.setState({
       this.setState({
         data,
         data,
         canvas,
         canvas,
         grid,
         grid,
+        categoryAmount,
         heatmapLayer: layer,
         heatmapLayer: layer,
         heatmapType: HeatmapType.Friendliness,
         heatmapType: HeatmapType.Friendliness,
-        mapLayer: this.showMap(canvas)
+        mapLayer: this.showMap(canvas),
       })
       })
     })
     })
   }
   }
@@ -286,13 +339,36 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
       heatmapType
       heatmapType
     });
     });
   }
   }
+
+  handleChangeColorByCat = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const type = parseInt(e.currentTarget.value) as unknown as CategoryType;
+    this.state.grid.forEach(({area, items}) => {
+      const xs = items.filter(i => i.data.firstCategory === type || i.data.secondCategory === type);
+      const opacity = OPACITY_RANGE(xs.length, this.state.categoryAmount[type]);
+      const color = new Color(`rgb(${opacity}, ${opacity}, ${opacity})`);
+      area.fillColor = color;
+      area.strokeColor = color;
+    });
+    this.setState({
+      heatmapType: type
+    })
+  }
   
   
+  heatmapByResponses = (type: HeatmapType | CategoryType): type is HeatmapType => {
+    return type === HeatmapType.Amount || type === HeatmapType.Correctness || type === HeatmapType.Friendliness || type === HeatmapType.Pleasantness || type === HeatmapType.Trustworthiness;
+  }
+
   render = () => (
   render = () => (
     <div className="vom-heatmap">
     <div className="vom-heatmap">
       <div className="vom-heatmap-map">
       <div className="vom-heatmap-map">
         <canvas id="vom-heatmap-canvas"></canvas>
         <canvas id="vom-heatmap-canvas"></canvas>
         {
         {
-          this.state.heatmapType !== HeatmapType.Amount ? (
+          this.state.heatmapType === HeatmapType.Amount ? (
+            <div id="vom-heatmap-amount-range">
+              <div>1</div>
+              <div>200</div>
+            </div>
+          ) : this.heatmapByResponses(this.state.heatmapType) ? (
             <div id="vom-heatmap-range">
             <div id="vom-heatmap-range">
               <div>1</div>
               <div>1</div>
               <div>2</div>
               <div>2</div>
@@ -303,15 +379,15 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
           ) : (
           ) : (
             <div id="vom-heatmap-amount-range">
             <div id="vom-heatmap-amount-range">
               <div>1</div>
               <div>1</div>
-              <div>200</div>
+              <div>{this.state.categoryAmount[this.state.heatmapType]} </div>
             </div>
             </div>
           )
           )
         }
         }
+        <Button size="sm" onClick={() => {this.saveAsImage()} }>download current map</Button>
       </div>
       </div>
       <div className="vom-heatmap-actions">
       <div className="vom-heatmap-actions">
         <FormGroup>
         <FormGroup>
-          <Button size="sm" onClick={() => {this.saveAsImage()} }>download current map</Button>
-          <h4>Heatmap type</h4> 
+          <h5>Heatmap based on responses</h5> 
           <div>
           <div>
             <CustomInput type="radio" id="heatmap-by-amount" name="by-amount" 
             <CustomInput type="radio" id="heatmap-by-amount" name="by-amount" 
             label="by amount of responses"
             label="by amount of responses"
@@ -346,6 +422,27 @@ class Heatmap extends Component<HeatmapProps, HeatmapState> {
           </div>
           </div>
         </FormGroup>
         </FormGroup>
       </div>
       </div>
+      <div className="vom-heatmap-actions">
+        <FormGroup>
+          <h5>Heatmap based on categorization</h5> 
+          <div>
+            {
+              CategoryTypeList.map(category => (
+                <CustomInput
+                  key={category}
+                  type="radio"
+                  id={`heatmap-by-${category}`}
+                  name={`by-${category}`}
+                  label={`${category}`}
+                  value={category}
+                  checked={this.state.heatmapType === category}
+                  onChange={this.handleChangeColorByCat}
+                />
+              ))
+            }
+          </div>
+        </FormGroup>
+      </div>
     </div>
     </div>
   )
   )
 }
 }