re-id热图
A strangely large number of companies I’ve worked with somehow inevitably get to the point where they want to build or integrate a heatmap of some kind.
与我合作的众多公司都不可避免地达到了他们想要建立或集成某种热图的地步。
In the past, I would reach for the nearest charting library like HighCharts. Why reinvent the wheel, and besides, their charting is cool!
过去,我会接触最近的图表库,例如HighCharts。 为什么要重新发明轮子,而且它们的图表很酷!
Over time, as I felt the itch to incorporate heatmaps into my weekend projects, I kept bumping it off due to the price. I kept asking myself “is this too expensive for a personal project?”
随着时间的流逝,当我感到渴望在周末的项目中加入热图时,由于价格的原因,我一直坚持不懈地努力。 我一直问自己“对于个人项目来说这太贵了吗?”
I don’t know about you, but for me the answer is yes. Some charting libraries charge a licensing fee of $500 per developer. This prices out almost every individual looking to use it for any “for-fun” usage. I lose far too much money buying SPY puts in the stock market to be able to afford spending $500 for a weekend project.
我不认识你,但对我来说答案是肯定的。 一些制图图书馆向每个开发人员收取500美元的许可费。 这几乎使希望将其用于任何“有乐趣的”用途的每个人都付出了代价。 我在股票市场上购买SPY期权损失的钱太多了,以至于没有能力花500美元购买一个周末项目。
So, like all engineers, I spent half an hour and made it myself.
因此,像所有工程师一样,我花了半个小时自己动手制作。
什么是热图? (What is a heatmap?)
A heatmap essentially shows a map and colors in various aspects of it based on some sort of scale.
热图本质上是基于某种比例显示图和其各个方面的颜色。
The “map” doesn’t necessarily have to be a map — it can be a periodic table or a bunch of circles. What really matters is that there are distinct areas that will be colored differently depending on what bucket that distinct area falls in to.
“地图”不必一定是地图,它可以是元素周期表或一堆圆。 真正重要的是,根据不同区域所属的存储桶,不同区域的颜色会有所不同。
组成部分 (The components)
There’s three aspects of a heatmap as far as web applications are concerned.
就Web应用程序而言,热图涉及三个方面。
- the map 地图
- the code 代码
- the data 数据
地图 (The map)
First off, you need an SVG of a map that you can color. I took this one from the Wikimedia Commons:
首先,您需要可以为地图着色的SVG。 我从Wikimedia Commons那里获得了这个:
分解SVG (Breaking down an SVG)
If you ever interacted with an SVG file, you may have noticed it behaves differently than other image files. Some sites don’t let you upload them, they maintain their quality when their size changes, and your browser seems to be the default file opener in a lot of cases.
如果您曾经与SVG文件进行过交互,则可能已经注意到它的行为与其他图像文件不同。 有些网站不允许您上传它们,它们会在其大小改变时保持其质量,并且在许多情况下,您的浏览器似乎是默认的文件打开器。
Well, that’s because they are different. SVGs actually consist of markup. If you inspect an SVG file, you’ll see all of the markup that makes up the SVG image.
好吧,因为它们是不同的。 SVG实际上包含标记。 如果检查SVG文件,您将看到组成SVG图像的所有标记。
All of that incomprehensible garbage is actually the data that then makes up the individual lines and shapes. This means that you can do a lot of things like interact with it using Javascript and CSS. It behaves like HTML, so you can add styles, ids, attributes, etc.
实际上,所有这些令人费解的垃圾都是构成单个线条和形状的数据。 这意味着您可以做很多事情,例如使用Javascript和CSS与之交互。 它的行为类似于HTML,因此您可以添加样式,ID,属性等。
That’s where the true power is.
那才是真正的力量所在。
准备您的SVG (Prepare your SVG)
So, let’s assume you found or created your SVG. If you create one, I don’t recommend coding it — use a drawing tool that will output an SVG.
因此,假设您找到或创建了SVG。 如果创建一个,则不建议对其进行编码-使用可输出SVG的绘图工具。
You need to prepare your SVG by adding identifiers to all of the various parts you want to independently color.
您需要通过向要独立着色的所有各个部分添加标识符来准备SVG。
The map above was particularly convenient — it was already categorized into states. For others, you may need to do it yourself.
上面的地图特别方便-它已经被分类为州。 对于其他人,您可能需要自己做。
Each of the individual <path> elements that make up a state had an ID attached to it with the ISO 3166 Alpha-2 code.
组成状态的每个单独的<path>元素都有一个附加的ID,并带有ISO 3166 Alpha-2代码。
Hawaii, for example, has an id of HI. If your map is missing it, simply add the id="HI" to the <path> that makes up Hawaii.
例如,夏威夷的ID为HI 。 如果您的地图丢失了,只需将id="HI"到组成夏威夷的<path>中。
代码 (The Code)
Now, let’s write the coloring function. It’ll be a function that takes an identifier and sets a color. We’ll flesh this out later once we structure the data appropriately — for now let’s stub it out and return a randomly selected color.
现在,让我们编写着色功能。 这将是一个使用标识符并设置颜色的函数。 一旦适当地构造了数据,我们将在稍后充实它-现在让我们将其存根并返回随机选择的颜色。
We don’t care about the color specifically or how it is chosen. What we are caring about is tying it together.
我们不在乎颜色或颜色如何选择。 我们关心的是将其绑在一起。
// map.js
function setColorFor(state) {
let color = Math.random() > 0.5 ? 'red' : 'blue'; let element = document.getElementById(state)
if (element) {
element.style.fill = color;
}
}setColorFor('HI');
将它们都放在HTML文件中 (Put them both in an HTML file)
Let’s go and put them together.
我们去把它们放在一起。
<!-- map.html -->
<html>
<body>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 930 585">
<!-- Put the rest of the SVG here... -->
</svg> <script type="text/javascript" src="map.js"></script>
</body>
</html>
You should see something like below:
您应该看到类似以下的内容:
数据 (The Data)
Now, we get to the final piece of the puzzle: the data.
现在,我们进入难题的最后一步:数据。
数据结构 (Structure of data)
We need to structure the data in a way we can push it to the map while maintaining domain independence. All this means is that we shouldn’t have to change the code to make it work with another use case.
我们需要以一种可以将其推送到地图的方式构造数据,同时保持域独立性。 所有这些意味着,我们不必更改代码即可使其与另一个用例一起使用。
Suppose we are using this map to visualize the extent of coronavirus cases in the United States. Let’s make up some numbers and see what a subset of this data would look like:
假设我们正在使用这张地图来可视化美国冠状病毒病例的范围。 让我们组成一些数字,看看这些数据的子集是什么样的:
let positiveTests = {
HI: 500,
WA: 1000,
MD: 10,
FL: 9001
}
Great! We clearly have a key-value pair of state code to value. Now, how do we bucket these into groups along a scale? I’m not a data algorithm expert, so we’ll do a bit of a janky approach.
大! 显然,我们有一对键值对状态代码。 现在,我们如何将它们按比例分组? 我不是数据算法专家,因此我们将做一些简单的方法。
首先计算我们要如何分配它 (First calculate how we want to distribute it)
The first decision to make — how many buckets do we want?
首先要做出的决定-我们要多少个水桶?
Let’s suppose we want to have the following breakdown:
假设我们要进行以下细分:
- High (≥ 5000) [Red] 高(≥5000)[红色]
- Moderate (≥ 1000 and < 5000) [Orange] 中度(≥1000 and <5000)[橙色]
- Low (≥ 500 and < 1000) [Yellow] 低(≥500并且<1000)[黄色]
- Minimal (< 500) [Green] 最小(<500)[绿色]
- Unknown [Grey] 未知[灰色]
We effectively have 5 buckets that we will divide the values into. This is what I will call the distribution scale (not the actual name since I’m not a data guy).
我们实际上有5个存储桶,可以将这些值划分为多个存储桶。 这就是我所说的分布范围(不是我的实际姓名,因为我不是数据专家)。
This distribution scale can be hard-coded as below:
可以如下硬编码此分配规模:
function getColorFor(value) {
if (!value) {
return 'grey';
}
else if (value >= 5000) {
return 'red';
}
else if (value >= 1000) {
return 'orange';
}
else if (value >= 500) {
return 'yellow'
}
else {
return 'green';
}
}
Now, let’s incorporate this getColorFor(value) function into the setColorFor(state) function we created above in our map.js script:
现在,让我们将此getColorFor(value)函数合并到我们在map.js脚本中上面创建的setColorFor(state)函数中:
// map.js
let data = {
HI: 500,
WA: 1000,
MD: 10,
FL: 9001
}function setColorFor(state) {
let color = getColorFor(data[state]);
let element = document.getElementById(state)
if (element) {
element.style.fill = color;
}
}function getColorFor(value) {
// ...
}setColorFor('HI');
At this point, Hawaii should turn yellow — this makes sense because our data says that Hawaii has ≥ 500 cases. However, all the other states in the dataset are still grey.
此时,夏威夷应该变成黄色-这是有道理的,因为我们的数据表明夏威夷有≥500例。 但是,数据集中的所有其他状态仍为灰色。
着色其他状态 (Coloring the other states)
The other states are still grey because the coloring of states is hard-coded.
其他状态仍为灰色,因为状态的着色是硬编码的。
The setColorFor('HI') line is the line of code that is triggering the color change. We could go and write out every single state, but that is boring and just poor engineering.
setColorFor('HI')行是触发颜色更改的代码行。 我们可以写出每个状态,但这很无聊,而且工程性很差。
Let’s go and make it so that we don’t have to list every state individually. Let’s remove setColorFor('HI') and replace it with the below code.
让我们开始吧,这样我们就不必单独列出每个州。 让我们删除setColorFor('HI')并将其替换为以下代码。
//map.js
// ...all other codefunction applyData(data) {
Object.keys(data).forEach(function(key) {
setColorFor(key);
});
}applyData(data);
Now we should see all the states we included in our dataset light up like a rainbow:
现在,我们应该看到我们包含在数据集中的所有状态都像彩虹一样亮着:
You can now see how you would start adding new colors, etc. It’s all very straightforward to do.
现在,您将看到如何开始添加新的颜色等。这一切都非常简单。
解决其他问题 (Addressing other concerns)
There’s some other areas of concern you may encounter while attempting to do this in a production environment.
在生产环境中尝试执行此操作时,可能还会遇到其他一些问题。
使其与Vue.js,React或Angular等框架一起使用 (Making it work with frameworks like Vue.js, React, or Angular)
I used vanilla JS and HTML in the examples to simply illustrate the concepts, but many modern web applications are written using actual frameworks and libraries.
我在示例中使用香草JS和HTML来简单说明概念,但是许多现代Web应用程序都是使用实际的框架和库编写的。
An application of the approach in a production environment would likely involve many other aspects such as componentization, theming, etc. and leverage actual frameworks or libraries.
该方法在生产环境中的应用可能涉及许多其他方面,例如组件化,主题化等,并利用实际的框架或库。
The great news is that you can easily follow the same exact technique and accomplish the goal regardless of the front-end technology. Vue, for example, allows you to use directive bindings directly on the SVG <path> elements.
好消息是,无论前端技术如何,您都可以轻松遵循相同的精确技术并实现目标。 例如,Vue允许您直接在SVG <path>元素上使用指令绑定。
Linked below is a Github repository I created illustrating the full approach describe above in vanilla Javascript, with an included sample of what it would look like as a Vue.js component:
下面链接的是我创建的Github存储库,该存储库说明了上面在Vanilla Javascript中描述的完整方法,并附带了一个作为Vue.js组件的示例:
自动缩放热图 (Automatically scaling the heatmap)
You may notice that, when resizing the browser window, your map doesn’t automatically adjust in size. You’ll need to do some extra configuration to make this work.
您可能会注意到,调整浏览器窗口大小时,地图不会自动调整大小。 您需要做一些额外的配置才能使这项工作生效。
For the map above, adding a viewbox attribute with a value of 0 0 930 585 to the SVG element made it scale appropriately for me (designers may shriek in terror at the eyeballing I did). Different contexts and SVGs will require different scaling approaches.
对于上面的地图,向SVG元素添加值为0 0 930 585的viewbox属性可以使其对我适当缩放(设计人员可能会对我的眼神感到恐惧)。 不同的上下文和SVG将需要不同的缩放方法。
If you want to learn all of the in-and-outs of SVG resizing and scaling, Amelia Bellamy-Royds wrote a fantastic article about in on CSS-Tricks:
如果您想了解SVG大小调整和缩放的所有内容,Amelia Bellamy-Royds在CSS-Tricks上撰写了一篇有关in的精彩文章:
创建线性分布 (Creating a linear distribution)
Perhaps you don’t want to pre-define the buckets, but rather divide the groups into a known number based on an interval that is dependent on the data.
也许您不想预先定义存储桶,而是根据依赖于数据的时间间隔将组划分为已知数。
You can do this by finding the maximum value in the dataset and then dividing it by the number of bucket you would like to have. This will give you the interval, which you could then color the sections by:
您可以通过在数据集中找到最大值,然后将其除以您想要的存储桶数来实现。 这将为您提供间隔,然后您可以通过以下方式为各部分着色:
function getInterval(numbers) {
const NUMBER_OF_BUCKETS = 3; let maximum = Math.max.apply(Math, numbers); return maximum / NUMBER_OF_BUCKETS;
}function getStyleFor(stateCode, valuesByStateCode) {
let interval = getInterval(Object.values(valuesByStateCode));
let value = valuesByStateCode[stateCode] if (value <= interval) {
return 'fill: #01C7E5';
}
else if (value <= interval * 1) {
return 'fill: #03C2DF'
}
else if { // ...and so on
}}
动态色彩和数据刻度 (Dynamic color and data scales)
You can dynamically set the color or data scale by accepting an ordered array of colors, or a function that determines it.
您可以通过接受颜色的有序数组或确定颜色的函数来动态设置颜色或数据比例。
If you truly wanted, you could make it fully data-driven. Something like below can be done:
如果您确实需要,可以使其完全由数据驱动。 可以执行以下操作:
let valuesByStateCode = {
HI: 30,
WA: 50,
FL: 2
};let buckets = [
{ color: 'green' },
{ color: 'blue' },
{ color: 'red' },
];function getInterval(numbers) {
let maximum = Math.max.apply(Math, numbers);
return maximum / buckets.length;
}function getStyleFor(stateCode) {
let interval = getInterval(Object.values(valuesByStateCode));
let value = valuesByStateCode[stateCode] let bucketIndex = Math.floor(value / interval);
let bucket = null; if (isNaN(value)) {
bucket = buckets[0];
}
else if (bucketIndex >= buckets.length) {
bucket = buckets[buckets.length - 1];
}
else {
bucket = buckets[bucketIndex]
} return `fill: ${bucket.color}`;
}
This code accepts an ordered array of colors and automatically figures out the number of buckets to divide the values into. Once that is done, it buckets the values received into those colors automatically.
该代码接受颜色的有序数组,并自动计算出将值划分为的存储桶数。 完成后,它将自动将接收到的值存储到这些颜色中。
You could then code further and create a gradient off a base color instead of having a pre-defined color scale if you wanted to, but I leave that as an exercise to the reader.
然后,您可以进一步编写代码,并根据基本颜色创建渐变,而不是使用预先定义的色标,但我将其作为练习留给读者。
就是这样! (And that’s about it!)
That’s the basics of creating a heatmap. Turns out you can do it in about half an hour and save yourself or your company thousands of dollars if cost is a concern and all you need are some basics.
这是创建热图的基础。 事实证明,如果您担心成本,而您所需要的只是一些基本知识,则可以在大约半小时内完成,并为自己或公司节省数千美元。
Note that you aren’t limited to maps of the United States. The technique illustrated above will work for pretty much any heatmap-style coloring. As long as you have a properly constructed SVG, you’re good to go.
请注意,您不仅限于美国地图。 上面说明的技术几乎适用于任何热图样式的着色。 只要您具有正确构造的SVG,就可以使用。
翻译自: https://medium.com/swlh/how-to-design-software-dynamic-heatmaps-e864742941b0
re-id热图




