Skip to content

Commit

Permalink
feat: new chart type - heatmap (#1370)
Browse files Browse the repository at this point in the history
* feat: new chart type - heatmap

* feat: heatmap axis and shape

* feat: shape and size

* feat: new chart type - heatmap

* feat: heatmap axis and shape

* feat: shape and size

* feat: heatmapStyle

* fix: merge v2

* fix: resolve test case

* fix: value map to size

* feat: sizeRatio and some optimization

* rename `shape` to `shapeType` to be consistent with 1.x
* optimized the implementation of heatmap adaptor
* add tuple type function
* add test cases size-spec

* fix: temporarily cancel legend

* fix: test cases with semantic test dataset
  • Loading branch information
neoddish authored and BBSQQ committed Aug 25, 2020
1 parent aa075ed commit 6f019e3
Show file tree
Hide file tree
Showing 18 changed files with 955 additions and 2 deletions.
63 changes: 63 additions & 0 deletions __tests__/data/basic-heatmap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export const basicHeatmapData = [
{ name: 0, day: 0, sales: 10 },
{ name: 0, day: 1, sales: 19 },
{ name: 0, day: 2, sales: 8 },
{ name: 0, day: 3, sales: 24 },
{ name: 0, day: 4, sales: 67 },
{ name: 1, day: 0, sales: 92 },
{ name: 1, day: 1, sales: 58 },
{ name: 1, day: 2, sales: 78 },
{ name: 1, day: 3, sales: 117 },
{ name: 1, day: 4, sales: 48 },
{ name: 2, day: 0, sales: 35 },
{ name: 2, day: 1, sales: 15 },
{ name: 2, day: 2, sales: 123 },
{ name: 2, day: 3, sales: 64 },
{ name: 2, day: 4, sales: 52 },
{ name: 3, day: 0, sales: 72 },
{ name: 3, day: 1, sales: 132 },
{ name: 3, day: 2, sales: 114 },
{ name: 3, day: 3, sales: 19 },
{ name: 3, day: 4, sales: 16 },
{ name: 4, day: 0, sales: 38 },
{ name: 4, day: 1, sales: 5 },
{ name: 4, day: 2, sales: 8 },
{ name: 4, day: 3, sales: 117 },
{ name: 4, day: 4, sales: 115 },
{ name: 5, day: 0, sales: 88 },
{ name: 5, day: 1, sales: 32 },
{ name: 5, day: 2, sales: 12 },
{ name: 5, day: 3, sales: 6 },
{ name: 5, day: 4, sales: 120 },
{ name: 6, day: 0, sales: 13 },
{ name: 6, day: 1, sales: 44 },
{ name: 6, day: 2, sales: 88 },
{ name: 6, day: 3, sales: 98 },
{ name: 6, day: 4, sales: 96 },
{ name: 7, day: 0, sales: 31 },
{ name: 7, day: 1, sales: 1 },
{ name: 7, day: 2, sales: 82 },
{ name: 7, day: 3, sales: 32 },
{ name: 7, day: 4, sales: 30 },
{ name: 8, day: 0, sales: 85 },
{ name: 8, day: 1, sales: 97 },
{ name: 8, day: 2, sales: 123 },
{ name: 8, day: 3, sales: 64 },
{ name: 8, day: 4, sales: 84 },
{ name: 9, day: 0, sales: 47 },
{ name: 9, day: 1, sales: 114 },
{ name: 9, day: 2, sales: 31 },
{ name: 9, day: 3, sales: 48 },
{ name: 9, day: 4, sales: 91 },
];

const NAMES = ['Alexander', 'Marie', 'Maximilian', 'Sophia', 'Lukas', 'Maria', 'Leon', 'Anna', 'Tim', 'Laura'];
const DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];

export const semanticBasicHeatmapData = basicHeatmapData.map((row) => {
return {
name: NAMES[row.name],
day: DAYS[row.day],
sales: row.sales,
};
});
74 changes: 74 additions & 0 deletions __tests__/unit/plots/heatmap/axis-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Heatmap } from '../../../../src';
import { semanticBasicHeatmapData } from '../../../data/basic-heatmap';
import { createDiv } from '../../../utils/dom';

describe('heatmap', () => {
it('x*y*color and default axis', () => {
const heatmap = new Heatmap(createDiv('default axis'), {
width: 400,
height: 300,
data: semanticBasicHeatmapData,
xField: 'name',
yField: 'day',
colorField: 'sales',
shapeType: 'circle',
label: {
offset: -2,
style: {
fill: '#fff',
shadowBlur: 2,
shadowColor: 'rgba(0, 0, 0, .45)',
},
},
});

heatmap.render();

// @ts-ignore
expect(heatmap.chart.options.axes.day.grid.alignTick).toBe(false);
// @ts-ignore
expect(heatmap.chart.options.axes.name.grid.alignTick).toBe(false);
});

it('x*y*color and custom axis', () => {
const heatmap = new Heatmap(createDiv('custom axis'), {
width: 400,
height: 300,
data: semanticBasicHeatmapData,
xField: 'name',
yField: 'day',
colorField: 'sales',
shapeType: 'circle',
label: {
offset: -2,
style: {
fill: '#fff',
shadowBlur: 2,
shadowColor: 'rgba(0, 0, 0, .45)',
},
},
xAxis: {
line: {
style: {
lineWidth: 2,
stroke: '#ff0000',
},
},
},
yAxis: {
label: {
style: {
fill: 'red',
},
},
},
});

heatmap.render();

// @ts-ignore
expect(heatmap.chart.options.axes.name.line.style.lineWidth).toBe(2);
// @ts-ignore
expect(heatmap.chart.options.axes.day.label.style.fill).toBe('red');
});
});
93 changes: 93 additions & 0 deletions __tests__/unit/plots/heatmap/index-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Heatmap } from '../../../../src';
import { basicHeatmapData, semanticBasicHeatmapData } from '../../../data/basic-heatmap';
import { createDiv } from '../../../utils/dom';
import { DEFAULT_COLORS } from '../../../../src/constant';

describe('heatmap', () => {
it('x*y with color', () => {
const heatmap = new Heatmap(createDiv('basic heatmap'), {
width: 400,
height: 300,
data: semanticBasicHeatmapData,
xField: 'name',
yField: 'day',
colorField: 'sales',
});

heatmap.render();

const geometry = heatmap.chart.geometries[0];

const { elements } = geometry;

let maxElementIndex = 0;
let minElementIndex = 0;

elements.forEach((e, i) => {
const value = e.getData().sales;
if (elements[maxElementIndex].getData().sales < value) {
maxElementIndex = i;
}
if (elements[minElementIndex].getData().sales > value) {
minElementIndex = i;
}
});

const colors = DEFAULT_COLORS.GRADIENT.CONTINUOUS.split('-');
expect(elements[maxElementIndex].getModel().color.toUpperCase()).toBe(colors[colors.length - 1]);
expect(elements[minElementIndex].getModel().color.toUpperCase()).toBe(colors[0]);
});

it('x*y with color and meta', () => {
const NAMES = ['Alexander', 'Marie', 'Maximilian', 'Sophia', 'Lukas', 'Maria', 'Leon', 'Anna', 'Tim', 'Laura'];
const DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];

const heatmap = new Heatmap(createDiv('basic heatmap by meta'), {
width: 400,
height: 300,
data: basicHeatmapData,
xField: 'name',
yField: 'day',
meta: {
name: {
type: 'cat',
values: NAMES,
},
day: {
type: 'cat',
values: DAYS,
},
},
colorField: 'sales',
});

heatmap.render();

const geometry = heatmap.chart.geometries[0];

expect(geometry.scales.name.isCategory).toBe(true);
expect(geometry.scales.name.values).toStrictEqual(NAMES);
expect(geometry.scales.day.isCategory).toBe(true);
expect(geometry.scales.day.values).toStrictEqual(DAYS);

const { elements } = geometry;

expect(elements.length).toBe(NAMES.length * DAYS.length);
let maxElementIndex = 0;
let minElementIndex = 0;

elements.forEach((e, i) => {
const value = e.getData().sales;
if (elements[maxElementIndex].getData().sales < value) {
maxElementIndex = i;
}
if (elements[minElementIndex].getData().sales > value) {
minElementIndex = i;
}
});

const colors = DEFAULT_COLORS.GRADIENT.CONTINUOUS.split('-');
expect(elements[maxElementIndex].getModel().color.toUpperCase()).toBe(colors[colors.length - 1]);
expect(elements[minElementIndex].getModel().color.toUpperCase()).toBe(colors[0]);
});
});
36 changes: 36 additions & 0 deletions __tests__/unit/plots/heatmap/label-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Heatmap } from '../../../../src';
import { semanticBasicHeatmapData } from '../../../data/basic-heatmap';
import { createDiv } from '../../../utils/dom';

describe('heatmap', () => {
it('x*y*color and label', () => {
const heatmap = new Heatmap(createDiv('label'), {
width: 400,
height: 300,
data: semanticBasicHeatmapData,
xField: 'name',
yField: 'day',
colorField: 'sales',
label: {
offset: -2,
style: {
fill: '#fff',
shadowBlur: 2,
shadowColor: 'rgba(0, 0, 0, .45)',
},
},
});

heatmap.render();

// @ts-ignore
expect(heatmap.chart.geometries[0].labelOption.cfg).toEqual({
offset: -2,
style: {
fill: '#fff',
shadowBlur: 2,
shadowColor: 'rgba(0, 0, 0, .45)',
},
});
});
});
92 changes: 92 additions & 0 deletions __tests__/unit/plots/heatmap/shape-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Heatmap } from '../../../../src';
import { semanticBasicHeatmapData } from '../../../data/basic-heatmap';
import { createDiv } from '../../../utils/dom';

describe('heatmap', () => {
it('x*y*color and shape', () => {
const heatmap = new Heatmap(createDiv('just change shape'), {
width: 400,
height: 300,
data: semanticBasicHeatmapData,
xField: 'name',
yField: 'day',
colorField: 'sales',
shapeType: 'square',
label: {
offset: -2,
style: {
fill: '#fff',
shadowBlur: 2,
shadowColor: 'rgba(0, 0, 0, .45)',
},
},
});

heatmap.render();

const geometry = heatmap.chart.geometries[0];
const { elements } = geometry;
expect(elements[0].shape.cfg.type).toEqual('rect');
});

it('x*y*color and size', () => {
const heatmap = new Heatmap(createDiv('just change size'), {
width: 400,
height: 300,
data: semanticBasicHeatmapData,
xField: 'name',
yField: 'day',
colorField: 'sales',
sizeField: 'sales',
label: {
offset: -2,
style: {
fill: '#fff',
shadowBlur: 2,
shadowColor: 'rgba(0, 0, 0, .45)',
},
},
});

heatmap.render();

const geometry = heatmap.chart.geometries[0];
expect(geometry.scales.sales.isContinuous).toBe(true);
// @ts-ignore
expect(geometry.attributeOption.shape.fields).toEqual(['sales']);

const { elements } = geometry;
expect(elements[0].shape.cfg.type).toEqual('rect');
});

it('x*y*color and shape*size', () => {
const heatmap = new Heatmap(createDiv('shape with size'), {
width: 400,
height: 300,
data: semanticBasicHeatmapData,
xField: 'name',
yField: 'day',
colorField: 'sales',
shapeType: 'circle',
sizeField: 'sales',
label: {
offset: -2,
style: {
fill: '#fff',
shadowBlur: 2,
shadowColor: 'rgba(0, 0, 0, .45)',
},
},
});

heatmap.render();

const geometry = heatmap.chart.geometries[0];
expect(geometry.scales.sales.isContinuous).toBe(true);
// @ts-ignore
expect(geometry.attributeOption.shape.fields).toEqual(['sales']);

const { elements } = geometry;
expect(elements[0].shape.cfg.type).toEqual('circle');
});
});
Loading

0 comments on commit 6f019e3

Please sign in to comment.