Recharts is a class library for chart processing. It uses the React feature to redefine the configuration and combination of charts, greatly improving the flexibility of chart customization. This article documents the development of custom style charts using Recharts in conjunction with SVG.
background
ABCmouse School edition provides teachers with a module that provides feedback on a child’s learning performance, some of which is presented visually in a graphic format.
This also moves into the realm of data visualization. This is an area that is too detailed to be considered by one person, and relies on third-party component libraries. Combined with this requirement, there are two main considerations in the selection of data visualization component library:
- Support the React
- Flexible custom styles are supported
After some research, Recharts[1] was selected to realize the above charts.
1. About Recharts
Recharts is a library for processing charts, and re stands for “redi25”, besides “React”, redefining the way the elements of a chart are combined and configured. It is built on React and D3 and has the following features:
-
Declarative tags make writing charts as easy as writing HTML
-
Configuration items that are close to native SVG make configuration items more natural
-
Interface API, to solve a variety of personalized needs
Here is an example of the output. The Code for Recharts is also very concise, avoiding the extra burden of learning a new set of configurations and apis.
<BarChart width={520} height={280} data={data}> <XAxis dataKey="scene" tickLine={false} axisLine={{ stroke: "#5dc1fb" }} tick={{ fill: "# 999" }} /> <Bar dataKey="time"isAnimationActive={! isEmpty} fill="#8884d8" barSize={32} shape={<CustomBar />} label={<CustomLabel />} > {data.map((entry, index) => ( <Cell key={index.toString()} /> ))} </Bar></BarChart>Copy the code
It can be said that each pain point is hit by it. For more detailed introduction, please refer to the author’s introduction article: Componentized Visual Charts – Recharts[2].
In the following part of this paper, record the detailed problems and implementation process of using it in the realization of pie charts and bar charts.
2. Pie chart implementation
As shown in the figure, the circle part of the PieChart here uses the PieChart component, while the text and legend in the middle are rendered directly using HTML without relying on Recharts.
To give you a sense of Recharts, here’s a quick look at how Recharts implements enlarged circles, guides, and labels.
2.1 Partially enlarge the ring
The Pie component provided by Recharts implements the basic circle part. If you need to customize the color, pass in the color of each pie chart through the Cell component.
<PieChart width={480} height={400}> <Pie data={data} dataKey="value" cx={200} cy={200} innerRadius={58} outerRadius={80} paddingAngle={0} fill="#a08bff" stroke="none" > {data.map((entry, index) => ( <Cell key={`cell-${index}`} fill={entry.color} /> ))} </Pie></PieChart>Copy the code
Get the ring:
Next, it is necessary to realize the effect of amplifying the corresponding Sector with a mouse in Hover state, and then displaying the dotted guide line and label.
To implement the Hover state for a Sector,
<Pie activeIndex={this.state.activeIndex} activeShape={renderActiveShape} data={data} dataKey="value" cx={200} cy={200} innerRadius={58} outerRadius={80} paddingAngle={0} fill="#a08bff" stroke="none" onMouseEnter={this.onPieEnter}> {data.map((entry, index) => ( <Cell key={`cell-${index}`} fill={entry.color} /> ))}</Pie>Copy the code
RenderActiveShape implementation first returns a Sector with a smaller inner diameter and a larger outer diameter. Fill the Sector component with the information returned by the Render function. Cx and cy are the coordinates of the center of the circle corresponding to the Sector.
functionrenderActiveShape(props) { const innerOffset = 2; // Inner const outerOffset = 4; Const {cx, cy, innerRadius, outerRadius, startAngle, endAngle, fill} = props;return( <Sector cx={cx} cy={cy} innerRadius={innerRadius - innerOffset} outerRadius={outerRadius + outerOffset} startAngle={startAngle} endAngle={endAngle} fill={fill} /> ); }Copy the code
Complete the circle part of the zoom effect:
2.2 Implementing boot Cables and Labels
I searched the document of Recharts and did not find the component of the bootline. The bootline of the example on the official website is a piece of code with embedded SVG elements. The author had not studied SVG graphics carefully before making this requirement. What to do? To learn!
I started a wave of Internet surfing and found the SVG tutorial of MDN [4]. After going through it, I had a basic impression. The
2.2.1 About Elements
The
A quick review of the documentation covers basic commands and accepted parameters:
M x y brush move to (x, y) as the starting point L x y Draw a line to (x, y)H x horizontally crossed to x xV y horizontally crossed to y y Close path back to the starting point (used to create a shape)Copy the code
It can also draw Bezier curves and arcs using the command below:
C x1 y1, x2 y2, x y Cubic Becselle curve Q x1 y1, x y quadratic Becselle curve A rx ry x-axis-rotation large-arc-flag sweep-flag x yCopy the code
For the D attribute, the commands covered in this article are already listed and will not be repeated here.
Design students need a dashed guide line, and SVG provides stroke-Dasharray to implement this requirement, which accepts a set of comma-separated numbers that represent a combination of line length and blank length.
Here, the raw materials needed to draw graphics are basically combed out.
2.2.2 Generating Path Data
Our goal was to output a Sector + bootstrap + Label in renderShapeData, generating the corresponding drawing data D by receiving input originally given only to the Sector. The observation is that we need a fold that goes out a little bit and then bends horizontally. That is to say we need to identify a starting point, a point of reference that is deflected in the middle, and a final destination. With the color style of the border, we get the following code. (This is how the renderActiveShape example on the official website was implemented, and what I have done here is to understand and modify it.)
<path d={`M${sx}.${sy} L${mx}.${my} L${ex}.${ey}`} stroke={fill} strokeDasharray="1, 3" fill="none"/>Copy the code
To determine the coordinates of the three points, we need to determine the meaning of the props properties when rendering activeShape.
const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, midAngle, fill, value, name} = props;Copy the code
The meanings of parameters such as center coordinates, Angle and radius involved are shown in the figure below:
Isn’t this the “right triangle” I learned in junior high school? The coordinates of the three points can be quickly calculated separately by using trigonometric functions.
Now turn all of this into code. Need to consider the Angle radian conversion, direction and other issues.
const RADIAN = Math.PI / 180; const innerOffset = 2; // Inner const outerOffset = 4; Const {cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle, fill, value, name,} = props; const {cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle, fill, value, name,} = props; const sin = Math.sin(-RADIAN * midAngle); const cos = Math.cos(-RADIAN * midAngle); const sx = cx + (outerRadius - innerOffset) * cos; const sy = cy + (outerRadius + outerOffset) * sin; const mx = cx + (outerRadius + outerOffset + 30) * cos; const my = cy + (outerRadius + outerOffset + 35) * sin; const ex = mx + (cos >= 0 ? 1: -1) * 80; const ey = my;Copy the code
At this point we render the desired bootline:
2.2.3 Generation of label
This step is relatively simple, just use the
element of SVG, use (ex, ey) as the starting coordinate of the text, and consider textAnchor to ensure alignment.
Final pie chart effect.
3. Bar chart implementation
Here, we need to make such a bar chart, involving two elements: the X-axis, a series of columns, and a React component each.
<BarChart width={520} height={280} data={data}> <XAxis dataKey="scene" tickLine={false} axisLine={{ stroke: "#5dc1fb" }} tick={{ fill: "# 999" }} /> <Bar dataKey="time" fill="#8884d8" barSize={32} /></BarChart>Copy the code
The following results are obtained:
At this point, we’re still short of the bar chart’s labels, gradients, and rounded corners at the top.
3.1 Implementation of gradient
First of all, we solved the problem of gradients. We searched the MDN document about gradients [6] and found that the implementation is actually very simple. Just insert a
The Recharts document does not mention the
element, see that all gradients, CSS, etc. in SVG are grouped in
at the beginning of the file. Imagination: if I write
directly inside the component, can it appear in the final generated < SVG >? Tried to write down, really! It shows that the imagination works.
Look, the JSX code with the gradient is still simple:
<BarChart width={520} height={280} data={data}> <defs> <linearGradient x1="0" x2="0" y1="0" y2="1"> <stop offset="0%" stopColor="#00ddee" /> <stop offset="100%" stopColor="#5dc1fb" /> </linearGradient> </defs> <XAxis dataKey="scene" tickLine={false} axisLine={{ stroke: "#5dc1fb" }} tick={{ fill: "# 999"}} / >... </BarChart>Copy the code
So easy~
3.2 Change the top to rounded corners
Next we implement the top of the rounded corner, which is essentially a closed
Here we use the shape property provided by the
component and pass in a custom component
to handle it.
<Bar dataKey="time" fill="url(#abc-bar-gradient)" barSize={32} shape={<CustomBar />}/>Copy the code
Next, we will focus on how to implement the
The implementation code is as follows. Once you understand the meaning of x, y, width, height, everything is very simple.
function CustomBar(props) { const { fill, x, y, width, height } = props; const radius = width / 2; const d = `M${x}.${y + height} L${x}.${y + radius} A${radius}.${radius} 0 0 1 ${x + width}.${y + radius} L${x + width}.${y + height} Z`; return ( <path d={d} stroke="none"fill={fill} /> ); }Copy the code
(x, y) refers to the coordinates of the top left corner of the column.
With rounded corners:
3.3 Setting the Shear
The above implementation is the case of relatively balanced data, when the data is very different, it will expose a psychological explosion problem, not to say more, see the figure below.
We want to implement a rounded rectangle, but (x, y) is actually in the upper left corner of the left margin of the semicircle. When this point is too close to the coordinate axis, plus the radius of the corner, the y-coordinate of the beginning of the corner is out of range, resulting in this weird situation. Is it possible to hide it?
How can you not! I continued to surf the Internet and found the clipping function of SVG [7]. It happened that SVG generated by Recharts also had
elements, which must have been considered by the author.
That is, I could have just referred to clipPath in the column, but it is prefixed with an id that seems to be globally uniform and self-incremented. How do I get the exact id?
Digging into the Recharts source code, we found the definition of clipPath’s ID mentioned here [8]. It turned out that we needed to pass in a fixed ID attribute on the outermost
<BarChart width={520} height={280} data={data} id={uniqueId}> ... </BarChart>Copy the code
Passing a clipPath with a combination of ids we can control to the
function CustomBar(props) { const { fill, x, y, width, height } = props; const radius = width / 2; const d = `M${x}.${y + height} L${x}.${y + radius} A${radius}.${radius} 0 0 1 ${x + width}.${y + radius} L${x + width}.${y + height} Z`; return ( <path d={d} stroke="none" fill={fill} clipPath={`url(#${uniqueId}-clip)`} /> ); }Copy the code
3.4 Implementation of Label
In the same vein, we define a
component.
<Bar isAnimationActive={! isEmpty} dataKey="time" fill="url(#abc-bar-gradient)" barSize={32} shape={<CustomBar />} label={<CustomLabel />}/>Copy the code
Use DevTools to track a wave and format the text (here abstracted as getStudyTime).
function CustomLabel(props) { const { x, y, width, height, value } = props; return ( <text x={x + width / 2 - 1} y={y - 10} width={width} height={height} fill="# 999" className="recharts-text recharts-label" textAnchor="middle"> {getStudyTime(value)} </text> ); };Copy the code
3.5 Final Effect
Summary and Impressions
SVG and React
When making this requirement, I also started to directly learn SVG and mastered a new technology to control visual display, which was full of harvest ~
React rendering SVG directly also further opened my horizon. It turns out that it can not only render HTML elements, but also directly render SVG. With the implementation of adaptation layer, we can also use Canvas and Native rendering, and even the LCD screen of embedded devices [9]. React implemented a set of code to construct a lot of complex UI logic on different platforms, and I really felt the power of this abstraction.
“Abstraction” and the selection of chart frame
During the vacation, I saw SICP course [10], which discussed many topics about “abstraction”. We build abstract barriers around complex things to keep our energy from being consumed by repetitive trivia.
The purpose of abstraction is to hide the complexity behind it. In essence, creating an abstract barrier also creates a new way of communication, which can be said to be a “language” in a sense.
Learning a new language can actually be a burden, but we don’t usually notice it. When such abstractions are complex enough, such burdens begin to manifest themselves. Often our needs cannot be met by a layer of abstraction, and often go across layers of abstraction barriers.
Crossing multiple layers of abstraction means that more “languages” and their relationships need to be understood at the same time, leading to a huge increase in complexity and a lot of pitfalls.
If you want to generalize complex reality in an abstract way, there will be certain emphasis in design. This is a paradox. A graphical visualization component like ECharts, which focuses on simple configurations, can be very difficult if you try to do subtle customization. Recharts is more focused on customization, which provides us with a way to directly touch the final UI presentation. With React, the customization process is easy enough. When we do component library selection, we should consider the comparison and tradeoff of objectives under different dimensions, and make the most appropriate choice according to the emphasis of requirements.
The resources
Recharts: http://recharts.org/
[2]
Modular visualization – Recharts: https://zhuanlan.zhihu.com/p/20641029
[3]
Website custom ActiveShape example: http://recharts.org/en-US/examples/CustomActiveShapePieChart
[4]
SVG tutorial: https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial
[5]
MDN about Stroke and the Fill is introduced: https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Fills_and_Strokes
[6]
MDN document about the gradient: https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Gradients
[7]
SVG shear function: https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Clipping_and_masking
[8]
The definition of clipPath id: https://github.com/recharts/recharts/blob/master/src/chart/generateCategoricalChart.tsx#L172
[9]
Render will React to the embedded LCD screen: https://juejin.cn/post/6844903984998809614
[10]
Bilibili Learning – SICP course: https://www.bilibili.com/video/av8515129/