如何在Angular中使用指令在表格中实现热图 2021-10-31 默认分类 暂无评论 1880 次阅读 ![1.jpeg][1] 让我们看看在Angular中使用指令为表格添加热图是多么容易。我们将采用一个非常简单而优雅的解决方案,为表格中的不同列设置不同的热图颜色。 正如我一直所说的,Directives是Angular的一个非常强大的功能。它可以作为一个优雅的解决方案来实现很多很酷的功能。当你到达文章的结尾时,你可以清楚地看到为什么指令的方法更有意义。 表格中的热图 ------ 尽管我们并不经常在表格中看到热图,但热图确实可以在可视化方面增加一些价值。在有某种比较或范围的数据集中,它将是有意义的。 ![2.jpeg][2] 为什么是指令? ------- 如果你有这样的疑问,这里有一些原因可以证明为什么创建一个指令来实现这个功能是有意义的。逻辑可以完全移到组件之外,使其更简单、更精简。如果逻辑从组件中分离出来,这意味着它更容易被重用。当一个东西以可重用的方式构建时,它将很容易扩展和维护。 热图逻辑 ---- 为了实现这个功能,让我们看看这里到底需要做什么。基本上,热图通过颜色或色调的变化让用户了解某物的大小。 所以,如果我们有一组数字。 [1,2,3,4,5,6,7,8,9,10] 在这里,基于数值,我们可以操纵颜色的强度。这意味着1将是颜色的最浅色,10将是颜色本身。所以我们只需要在这里将数值映射到颜色的强度。我们也可以有相反的条件。 有不同的方法来实现这一点。 使用Alpha通道 --------- 我们可以很容易地使用RGBA或HSLA来实现热图,只需改变意味着颜色透明度的alpha通道。 我不打算使用这种方法,因为我们还试图根据背景颜色生成可访问的文本颜色。这将确保文本在所有的色块中都保持可读性。 使用HSL颜色表达式 ---------- 在这里,我将使用HSL颜色表达式,通过操作L(亮度)参数,轻松获得每个值的正确颜色。HSL是一种非常好的表达颜色的方式,用它来操作颜色是非常容易的。 **HSL**代表色相饱和度亮度,它还可以有一个Alpha通道,即**HSLA**。 所以这里的想法是找到每个值的亮度系数。以下是我们如何做的。 所以这里的原始颜色值首先被解析为HSLA。 HSLA(234, 77%, 46%, 1) --> Lightness = 46%。 我们有最小可能的亮度值,即0.46。所以最高值的亮度为46%,其他值的亮度会更高。当亮度增加时,它就会向白色靠拢。 下面是这个公式: const color = '#1b2dd0'; const [h,s,l,a] = parseHSLA(color); // <-- [234, 0.77,0.46,1] const highestValue = 10; const maxLightness = 1 - l; // <-- 1 - 0.46 = 0.54 const lightness = 1 - (value * maxLightness / highestValue); // 1 --> 1 - (1 * 0.54 / 10) = (1 - 0.05) ~ 95% // 5 --> 1 - (5 * 0.46 / 10) = (1 - 0.23) ~ 77% // 10 -> 1 - (10 * 0.54 / 10) = (1 - 0.54) ~ 46% 在这里,10将是最低的数字,因此我们需要一个非常浅的颜色,所以95%将使它非常浅。亮度%随着它的增加会使颜色变白。 ![3.jpeg][3] 所以,现在我们已经有了逻辑,让我们开始使用指令吧! 创建热图指令 ------ 所以我提到了 "指令"(复数),因为我们将为这个功能创建多个指令。具体说来,是3个。在这3个指令中,有两个只是用于标记元素和设置一些元数据。 1. 热图表 2. 热图列 3. 热图单元格 下面是我们如何在模板中使用这些指令: ![Screenshot.png][4] 热图单元格指令 ------ @Directive({ selector: '[heatMapCell]', }) export class HeatmapCellDirective { @Input('heatMapCell') heatMap = 0; @Input('id') colId = null; constructor(public el: ElementRef) {} } 我们有一个输入,将值传入指令,同时也接受单元格在表中所属列的id。我们注入ElementRef,这样我们就可以在以后操作该元素。 热图列指令 ----- @Directive({ selector: '[heatMapColumn]', }) export class HeatmapColumnDirective { @Input('id') colId = null; @Input('heatMapColumn') options = {}; } 在这里,我们可以传递样式的选项,如颜色等,也可以传递列的ID。 热图表指令 ----- 这是最主要的指令,所有的工作都在这里完成。这个指令被放置在表格上。而其他指令则是放在列和单元格上。 在这里我们可以看到我们如何使用ContentChildren从父指令中访问子指令。 @Directive({ selector: '[heatMapTable]', }) export class HeatmapTableDirective implements AfterViewInit { @ContentChildren(HeatmapCellDirective, { descendants: true }) heatMapCells: QueryList; // <-- Get all the cells @ContentChildren(HeatmapColumnDirective, { descendants: true }) heatMapColumns: QueryList; // <-- Get all the columns highestValues = {}; cells: HeatmapCellDirective[] = []; columns: HeatmapColumnDirective[] = []; config = {}; ngAfterViewInit() { this.cells = this.heatMapCells.toArray(); this.columns = this.heatMapColumns.toArray(); this.setOptions(); this.calculateHighestValues(); this.applyHeatMap(); } private setOptions() { this.columns.forEach((col) => { this.config = { ...this.config, [col.colId]: col.options, }; }); } private calculateHighestValues() { return this.cells.forEach(({ colId, heatMap }) => { if (!Object.prototype.hasOwnProperty.call(this.highestValues, colId)) { this.highestValues[colId] = 0; } if (heatMap > this.highestValues?.[colId]) this.highestValues[colId] = heatMap; }); } private applyHeatMap() { this.cells.forEach((cell) => { const { bgColor, color } = this.getColor(cell.colId, cell.heatMap); if (bgColor) cell.el.nativeElement.style.backgroundColor = bgColor; if (color) cell.el.nativeElement.style.color = color; }); } private getColor(id: string, value: number) { const color = this.config[id].color; let textColor = null; let bgColor = null; if (color != null) { const [h, s, l, a] = parseToHsla(color); const maxLightness = 1 - l; const percentage = (value * maxLightness) / this.highestValues[id]; const lightness = +percentage.toFixed(3); bgColor = hsla(h, s, 1 - lightness, a); textColor = readableColor(bgColor); } return { bgColor, color: textColor, }; } 让我来分解一下代码。 **获取对单元格和列的访问权** 我们可以访问需要应用热图的单元格。 @ContentChildren(HeatmapCellDirective, { descendants: true }) heatMapCells: QueryList; 这个heatMapCells变量将有应用heatMapCell的td列表。请确保设置{ descendants: true }。 注意:如果为真,则包括该元素的所有子孙。如果是false,则只查询该元素的直接子女。 **保存每一列的选项** 我们可以在一个对象中保存为每一列提供的选项。目前,我们只配置了颜色,但这个对象可以用来为每一列定制热图的各种不同选项。 config = { "employees": { "color": "#000fff" }, "contractors": { "color": "#309c39" } } **计算每一列的最高值** 我们现在可以计算出每一列的最高值,并将其保存在一个以colId为键的对象中。 highestValues = { employees: 1239, contractors: 453 } **应用热图样式** 我们现在可以循环浏览单元格,然后给单元格应用backgroundColor和color。由于我们已经在单元格中注入了ElementRef,我们可以使用el属性来修改样式。 cell.el.nativeElement.style.backgroundColor = 'blue'; 我们有一个辅助函数,根据我们上面讨论的逻辑为每个单元格找到颜色: private getColor(id: string, value: number) { const color = this.config[id].color; let textColor = null; let bgColor = null; if (color != null) { const [h, s, l, a] = parseToHsla(color); const maxLightness = 1 - l; const percentage = (value * maxLightness) / this.highestValues[id]; const lightness = +percentage.toFixed(3); bgColor = hsla(h, s, 1 - lightness, a); textColor = readableColor(bgColor); } return { bgColor, color: textColor, }; } 颜色处理是通过一个超级简单的库color2k来完成的,它提供了很多处理颜色的工具。 我们使用了一个叫做readableColor()的东西,它根据给定颜色的亮度返回黑色或白色以获得最佳对比度。这将使我们的热力图更容易理解。 演示和代码 ----- ![Screenshot.png][5] 最后的思考 ----- 正如你所看到的,该组件中没有太多的代码。所有的逻辑都在指令中得到了很好的处理。指令中唯一复杂的事情是寻找颜色。其他的都是直接的。 这是一个非常基本的实现,也不完美。为了让它变得更好,我们可能还需要添加一些验证和错误处理。另外,这还可以通过提供更多的选项进行扩展,比如升/降热图、颜色范围、正负热图等等。 这篇博文的整体思路是展示如何使用指令来实现这一功能。 [1]: http://guobacai.com/usr/uploads/2021/10/3373672669.jpeg [2]: http://guobacai.com/usr/uploads/2021/10/304722231.jpeg [3]: http://guobacai.com/usr/uploads/2021/10/2527518492.jpeg [4]: http://guobacai.com/usr/uploads/2021/10/871345438.png [5]: http://guobacai.com/usr/uploads/2021/10/3283548598.png 标签: 组件, Angular, 验证, value, color, const, 指令, bgcolor, 颜色
评论已关闭