We've all followed Angular tutorials, and the end result is usually a neatly-formatted "List component" screen showing a list of data, perhaps with a "Details component" to see further details about a chosen object. But wouldn't it be cool if we could get Angular to create beautiful, responsive diagrams instead? That's what we're going to create here.
Obviously, this looks great, but won't be suitable for all scenarios or types of data, but hopefully this article will teach you some useful techniques, and show you what is possible with Angular and SVG, without too much coding.
We will read in some JSON data, and use Angular to bind to SVG elements to create a diagram using SVG. elements have a big advantage over elements as they're much less memory hungry, and, because they are vector graphics, you can zoom in on them without losing image quality.
You can see a "live" version of this website by clicking here. You'll see that, in this version, I've taken it further, allowing you to drag the SVG control around, and zoom in & out, either using the zoom icons, or your mouse wheel. You can also click on an employee to bring up their details.
TypeScript, GIT, npm, and the Angular CLI installed 安装了TypeScript,GIT,npm和Angular CLI
the resources in the assets.zip file, which you can download at the top of this article
可以在本文顶部下载的assets.zip文件中的资源
basic knowledge of Angular development Angular开发的基础知识
My original mock-up for this project also included a Web API project to read the data directly from SQL Server. I have replaced this with a hardcoded set of JSON data to keep this article more concise, but if you are interested in knowing how to create a Web API using ASP.NET Core 2, you can read my other Code Project article here.
You'll also notice that I'm cheating a little bit: each of the Employee records already has an x and y coordinate. We're not going to write code to take a set of hierarchical data, and work out where to place it on a new diagram. Everyone's data will look different, and this wouldn't add much value to the article.
This article is more to give you creative ideas which you can use in your own projects, rather than being a step-by-step introduction to Angular. And there seem to be very few examples showing how to use Angular and SVG together. I hope you find this interesting article useful, please remember to leave a comment if you do!
A few minutes later, I have a c:\repos\Southwind folder, containing 31,000 files. Okay, let's go to that folder, and start Visual Studio Code:
几分钟后,我有了一个c:\ repos \ Southwind文件夹,其中包含31,000个文件。 好的,我们转到该文件夹,然后启动Visual Studio Code:
cd Southwind
code .
Now, by default, all Angular apps will run on port 4200. I like to change this, with each new project, just to make sure there's no clashes. To do this, let's open up package.json in Visual Studio Code, open the "package.json" file, and change the "start" script to specify a different port number:
If you were to now run the app, you'd see a fairly dull screen, showing four columns. When you resize the browser window to a smaller width, the columns will stack one on top of another.
Yeah, it's dumb... we're meant to be learning SVG here (!), but honestly, Angular has been changing so rapidly and dramatically over the past few years, I always like to create apps step-by-step and test them along the way, to make sure nothing's quietly been changed.
Okay, assuming this is all working fine, let's add some resources to the app. Our little app will contain some employees data, and photos for each of them. I've also included some icons that I've used in the "full version" of this app, which you can find online.
At the top of this tutorial, you'll find an assets.zip file. Go ahead and unzip this into your src\assets folder. You should then see these 3 folders and their files, in Visual Studio code:
In the assets\SampleData folder, you'll see a new employees.json file. This contains the list of employee records, and all a list of relationship records - which employees are the managers or sub-managers of other employees? The structure looks like this:
In the relationship table, we have an employeeId and managerId value, each of which are foreign keys, linking back to employee records. It also contains a type string, which is either "Manager" or "Sub-manager", which we'll use later, just to show how we can easily change the appearance of our diagram's lines to reflect different types of relationships.
(Yes, I know, those two interfaces should be in separate files... but I'm trying to keep this concise !)
(是的,我知道,这两个interfaces应该在单独的文件中...但是我想保持简洁!)
What we're doing here is defining two new interfaces which describe how our json data looks like, and importing the json data into two variables, employees and relationships.
However, there's a warning at the top of this file.
但是,此文件的顶部有一个警告。
By default, Angular can't import data directly from a json file. To fix this, simply open the tsconfig.json file (in the root of the project) and add two lines, under "compilerOptions":
Notice that I've displayed these records using the Bootstrap Cards styling. This is a really useful set of classes, particularly when making responsive websites which still look good on a tablet/smartphone screen. You can read more about Bootstrap Cards here.
Just like before, we're using a *ngFor to iterate through our list of employees, and creating an image and two text elements for each of them, in the correct position on our grid.
For me, the pain point in creating that code above is all in the details. How do I set the URL of the image to be displayed? (Answer: We use an attr.xlink.href attribute.) How do I center the text just below the image? (Answer: We use dominant-baseline and text-anchor attributes to handle centering for us.) And so on.
Life would've been a whole lot easier, if I just had a working example to learn from... ;-)
如果我只有一个工作的例子可以向...学习,那么生活会容易得多。
Internet Explorer 11 (Internet Explorer 11)
Oh dear. Even in 2019, dear old IE is still giving us problems. The code so far works fine on all modern browsers, but to use it on Internet Explorer 11, we need to do a few extra steps. (On my Windows 10 machine, this app hung IE11 completely. I had to use Task Manager to kill it.)
噢亲爱的。 即使在2019年,亲爱的旧IE仍然给我们带来了问题。 到目前为止,该代码在所有现代浏览器上都可以正常运行,但是要在Internet Explorer 11上使用它,我们需要做一些额外的步骤。 (在我的Windows 10计算机上,此应用程序完全挂起了 IE11。我必须使用任务管理器将其杀死。)
Angular will have given you a src\polyfills.ts file, and this hints at the first change which is required. It tells you:
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
You need to uncomment this import line, and run the command it mentions:
您需要取消注释此import行,并运行它提到的命令:
npm install --save classlist.js
Next, hop across to the tsconfig.json file in the root directory and change this line:
接下来, 跳至根目录中的tsconfig.json文件,并更改以下行:
"target": "es2015",
to this:
对此:
"target": "es5",
Finally, in the browserslist file, also in the root directory, change the line from:
最后,在浏览器列表文件中,也在根目录中,将行更改为:
not IE 9-11 # For IE 9-11 support, remove 'not'.
...to...
...至...
IE 9-11 # For IE 9-11 support, remove 'not'.
With these changes in place, the SVG view will be shown in all of the browsers. You will need to stop running your app, and run npm start again, fo these changes to kick in.
Note that these tips apply to any Angular application which use SVG controls... so put a bookmark on this page... you'll be needing this for your other apps !
And remember, many large companies still insist on having only Internet Explorer installed on their employees laptops, and the users won't have Admin rights to install any other browsers on their machines. So, you might want to follow these tips for all of your projects, unless you are sure that all of your users do definitely have a modern browser installed.
Next, let's add the lines between the images, to show who is the manager of who. To do this, we need to add two simple functions to our src\app.component.ts file. Each of our relationship records references the ids of two Employee records, and we'll need to find these Employee records, to get their (x,y) coordinates.
Remember, when we iterate through the relationship records, we will only have the employee id value - we need to look up the relevant employee record from here.
The second function is simply a boolean function, to say whether a relationship is for a "Manager" or a "Sub-manager". We will get Angular to display dashed lines for the latter, by applying a SVGlineDashed class to these particular lines.
So, in the src\app.component.ts file, let's add these two functions:
因此,在src \ app.component.ts文件中,我们添加以下两个功能:
public GetEmployee(id) {
// Search for, and return, an Employee record, with a particular id value
const employee = this.employees.filter(emp => {
return emp.id === id;
});
if (employee == null) {
return null;
}
return employee[0];
}
public IsSubManager(relationshipType) {
return (relationshipType === 'Sub-manager');
}
I'm also going to add some CSS to our src\app.component.scss file:
我还将在我们的src \ app.component.scss文件中添加一些CSS:
.SVGline {
/* What color line do we want, to connect each of our circular employee image photos ? */
stroke: #141;
stroke-width: 3;
}
.SVGlineDashed {
/* What type of line shall we use, to connect an employee to a Sub-Manager ? */
stroke-dasharray: 3,3;
}
With the two functions and CSS in place, let's add the following lines to src\app.component.html, just after the attribute:
So, each of our employee images is 100x100 pixels in size. We'll get our SVG to display lines in between certain employees from the center of this rectangle. And, as you can see, each line requires a "start" (x, y) coordinate and an "end" (x,y) coordinate.
因此,我们每张员工图片的大小均为100x100像素。 我们将使SVG在此矩形的中心显示某些员工之间的线。 而且,如您所见,每行都需要一个“ start ”(x,y)坐标和一个“ end ”(x,y)坐标。
With this in place, our webpage now shows the lines nicely, with our two Sub-manager lines shown as dotted, thanks to that SVGlineDashed CSS class.
You should now understand why, in the control, we draw the lines first. If we had drawn the images first, the lines would have appeared on top of the employee's faces.
Well, we achieved our goal, we have a diagram of images using SVG.... but it really looks a bit naff. The first way we can improve this is to crop each of the images in a circle. To do this, we will:
draw a circle where the image will appear 在将要出现图像的地方画一个圆圈
define a circular clipPath, with a unique name (based on the *ngFor iterator index)
定义一个具有唯一名称的圆形clipPath (基于*ngFor iterator索引)
draw the image, using the clipPath we've just created
使用我们刚刚创建的clipPath绘制图像
Again, if you've never seen this code before, it might be a little strange to you, but try adding it to your own code, and see how it looks. Personally, it took me a few attempts (and a lot of Googling) to find out how to create these clipPaths, and how to use them properly.
To add this circular cropping, we just need to remove the lines that currently display an employee's image:
要添加此循环裁剪,我们只需要删除当前显示员工图像的行:
... and then replace the *ngFor which iterates over the employee records with this code:
...然后使用以下代码替换用于遍历employee记录的*ngFor :
With these changes in place, our diagram looks more professional:
完成这些更改后,我们的图看起来更加专业:
The next thing I'm going to do is turn our diagram into "dark mode". It's personal preference, but I think it looks much smarter with a dark (gradient) background.
接下来要做的是将图表转换为“暗模式”。 这是个人喜好,但我认为在深色(渐变)背景下看起来更聪明。
I actually set our two text elements (for the employee's name and job) to use specific CSS classes, but we hadn't actually defined them yet. We'll do that in the following step.
First, in the app.component.html file, we'll wrap the element in the following HTML. Notice that I've also added an extra style.format attribute to our element. You'll see why in a minute.
By the way, I've done loads of CSS over the years, and in the changes above, I slipped in my favourite trick. Those three "zoom" icons each have the CSS class "button", and with the following CSS, when you tap/click on the button, they shrink momentarily. It really adds a professional touch to the page - the user feels like the button is responding to their click.
.button:active {
/* When we click on the ZoomIn / ZoomOut button, briefly make the icon shrink. */
transform: scale(0.9);
}
Moving on, wouldn't it be cool if we could zoom in and out that using the mouse-wheel? We can. And it's dead easy.
继续前进,如果我们可以使用鼠标滚轮进行放大和缩小,那不是很酷吗? 我们可以。 而且非常容易。
Simply add the following attribute to the control:
只需将以下属性添加到控件中:
... then add a function to test if we're scrolling up or down on the mousewheel, to call the relevant function:
...然后添加一个函数来测试我们是否在mousewheel上向上或向下滚动,以调用相关函数:
public OnSVGmousewheel(event: MouseWheelEvent) {
if (event.deltaY < 0) {
this.ZoomIn();
} else {
this.ZoomOut();
}
return false;
}
Okay, it always zooms in and out to the centre of the control, but it looks pretty good, no ?
好的,它总是放大和缩小到控件的中心 ,但是看起来还不错,不是吗?
Time for another really cool CSS trick ! Right now, when you zoom in or out, it jumps to that zoom-level, rather than smoothly animating to the zoom level. To fix this, we just need to add these lines to the src\app.component.scss file:
This, again, is one of those little tricks that really makes a difference in a web app.
同样,这是在网络应用中真正发挥作用的小技巧之一。
正在搜寻 (Searching)
Next, let's add a Search function to our app. Now, we're going to have a search textbox on the screen, which we're going to want to bind to a property in our component, so the first thing we need to do is add some lines to the app.module.ts file to say that we'll be using ngModel. First, we need to import the FormsModule into our app:
This code will iterate through our list of employee records, and will either set or unset the highlighted value, to say if they match our search string. Back in the src\app.component.html file, we need to add a CSS class to our employee image/name/job elements for the employees who do match this search string, by adding an ngClass to our existing grouping element:
And then we need to add a class to slightly "darken" any employees who don't match our search string.
然后,我们需要添加一个类别,以使所有与我们的搜索字符串不匹配的员工稍微“变暗”。
.NotSearchResult {
opacity: 0.4;
}
With these changes in place, we have a responsive search function. As you start typing in the textbox, straightaway, and employees who don't match your string get darker, highlighting the employees who do match. Again, you can see this in action in the live version of this website.
There's just one slight problem: when we first go into this screen, none of the employees have a highlighted value set, so they're all dark! To fix this, we just need to remember to call our FilterBySearch function in our constructor.
constructor() {
// Populate our two arrays from our sample-data .json file
this.employees = employeeData.employees;
this.relationships = employeeData.relationships;
this.FilterBySearch();
}
And that's it. We now have a cool, responsive, search function.
就是这样。 现在,我们有了一个很酷的响应式搜索功能。
添加一个弹出(模式)组件 (Adding a popup (modal) Component)
The diagram looks great now, a world away from a regular Angular webpage, but it would be nice if we could click on an employee and see extra details about them. Obviously, the easiest way would be to add a link to our element to redirect to a different page:
However, let's just talk through how you'd add a popup Component to the app. This is one of those Angular topics which is straightforward if you follow the correct steps, but will tie you up in knots if you haven't seen a working example.
Here's what the end result will look like, after I've clicked on Monica Eichofsen:
单击Monica Eichofsen之后,最终结果将是这样:
First, we will need to create a new Component which will get displayed when we click on an employee. In Visual Studio's Terminal window, hit CTRL+C if you're currently running the app, then add a new component using:
... but we also need to add it to a new section, entryComponents:
...但是我们还需要将其添加到新部分entryComponents :
entryComponents: [
// Any modal components need to go in here, aswell as in the "declarations" section
CustomerDetailsComponent
],
providers: [],
(I've just saved you half an hour of Googling, when your modal component refused to be shown at runtime!)
(当您的模态组件拒绝在运行时显示时,我刚刚为您节省了半小时的Google搜索时间!)
Okay. Our app now knows about Google Materials. Let's hop over to our new customer-details\customer-details.component.html file, and change it to display our employee's details as a Bootstrap card:
Notice how we've had to inject MAT_DIALOG_DATA, to allow us to pass data into this component, as we'll want to tell it which Employee record we want to display.
Finally, we need to make some changes to app.component.ts. First, let's add the dependencies at the top of the file.
最后,我们需要对app.component.ts进行一些更改。 首先,让我们在文件顶部添加依赖项。
import { MatDialog } from '@angular/material';
import { CustomerDetailsComponent } from './customer-details/customer-details.component';
We need to modify our constructor, ready for us to use the Materials class:
我们需要修改构造函数,以准备使用Materials类:
constructor(private dialog: MatDialog) {
And with all that in place, we can now add our SelectEmployee function. We just get Angular Materials to open an instance of our CustomerDetailsComponent, and pass a copy of our chosen Employee record to it:
Yup, it's fiddly remembering all of these steps, but it keeps our page clean and responsive, and this should give you some inspiration and ideas for how you could implement your own diagram/workflow pages.
You might have noticed that the online version of this webpage also allows you to drag around the diagram, which is really essential when you've zoomed in. The key to this is simply making the control position:absolute and altering the top and left values as you drag.
This is easy enough to do, and I'll include the full source code in the download version at the top of the article, but outside the scope for this article.
这很容易做到,我将在文章顶部的下载版本中包含完整的源代码,但不在本文的讨论范围之内。
What's important is that you see how incredibly easy it is to take real data, and make beautiful diagrams or charts out of it, using Angular and SVG.
重要的是,您将看到使用Angular和SVG采集真实数据并制作精美的图表非常容易。
最后的想法 (Final Thoughts)
What I didn't tell you was that many years ago, I created a similar HTML/SVG webpage for the financial company I was working for, to display a workflow diagram. It was really cool, you could drag'n'drop elements - but - this was in the days before AngularJS or React - the code was a lengthy mess of JavaScript code. Forget about ever running continuous testing on that !
With Angular, as you've seen, you can nicely split up the TypeScript code from the HTML code, keeping it maintainable and manageable. And the end result looks really nice.
原题链接:#137 Single Number II
要求:
给定一个整型数组,其中除了一个元素之外,每个元素都出现三次。找出这个元素
注意:算法的时间复杂度应为O(n),最好不使用额外的内存空间
难度:中等
分析:
与#136类似,都是考察位运算。不过出现两次的可以使用异或运算的特性 n XOR n = 0, n XOR 0 = n,即某一
A message containing letters from A-Z is being encoded to numbers using the following mapping:
'A' -> 1
'B' -> 2
...
'Z' -> 26
Given an encoded message containing digits, det