Who likes charts? Everyone likes it, right? There are many ways to create charts, including many libraries. For example, D3.js, Chart.js, amCharts, Highcharts and Chartist, which are just a few of the many options.
But we don't necessarily need a chart library to create charts. Mobx-state-tree (MST) is an intuitive alternative to Redux to manage state in React. We can build interactive custom charts using simple SVG elements and use MST to manage and manipulate the data of the chart. If you have tried building charts with tools like D3.js in the past, I think you'll find this approach more intuitive. Even if you are an experienced D3.js developer, I still think you will be interested in the power of MST as a visual data architecture.
Here is an example of MST using to drive charts:
This example uses the scale function of D3, but the chart itself is rendered using the SVG element in JSX. I don't know of any chart library with the option to flash hamster points, so this is a great example of why building your own chart is great – and it's not as difficult as you think!
I've been building charts with D3 for over 10 years and while I love the power of it, I always find that my code ends up being clumsy and hard to maintain, especially when dealing with complex visualizations. MST completely changes all this by providing an elegant way to separate data processing from rendering. I hope this article encourages you to give it a try.
Familiar with MST models
First, let's give a quick overview of the appearance of the MST model. This is not an in-depth tutorial on everything about MST. I just want to show the basics because in reality you only need that basics 90% of the time.
Here is a Sandbox with code for a simple to-do list built using MST. A quick look and I'll explain what each section does.
First, the shape of the object is defined by the typed definition of the model attributes. Simply put, this means that instances of the to-do model must have a title, which must be a string, and by default the "done" property is false.
<code>.model("Todo", { title: types.string, done: false //这相当于types.boolean,默认为false })</code>
Next, we have the views and manipulation functions. A view function is a method of accessing calculated values based on data in the model without changing the data saved by the model. You can think of them as read-only functions.
<code>.views(self => ({ outstandingTodoCount() { return self.todos.length - self.todos.filter(t => t.done).length; } }))</code>
On the other hand, operation functions allow us to safely update data. This is always done in the background in a non-mutable way.
<code>.actions(self => ({ addTodo(title) { self.todos.push({ id: Math.random(), title }); } }));</code>
Finally, we create a new storage instance:
<code>const todoStore = TodoStore.create({ todos: [ { title: "foo", done: false } ] });</code>
To show what the memory actually does, I added some console logs to show the output of outStandingTodoCount() before and after the toggle function that triggers the first instance of Todo.
<code>console.log(todoStore.outstandingTodoCount()); // 输出:1 todoStore.todos[0].toggle(); console.log(todoStore.outstandingTodoCount()); // 输出:0</code>
As you can see, MST provides us with a data structure that allows us to easily access and manipulate data. More importantly, its structure is very intuitive and the code is clear at a glance - there is no reducer!
Let's create a React chart component
OK, now that we have understood what MST looks like, let's use it to create a memory that manages the data of the chart. However, we will start with chart JSX, because once we know what data is needed, it is much easier to build memory.
Let's take a look at JSX that renders the chart.
The first thing to note is that we are using styled-components to organize our CSS. If you are not familiar with it, Cliff Hall has a great article showing its use in React applications.
First, we are rendering the drop-down menu that will change the axes of the chart. This is a fairly simple HTML drop-down menu wrapped in a styled component. It should be noted that this is a controlled input whose state is set using the selectedAxes value in our model (we will cover this later).
<code>model.setSelectedAxes(parseInt(e.target.value, 10)) } defaultValue={model.selectedAxes} ></code>
Next is the chart itself. I've split the axes and points into their own components, which are in separate files. By keeping each file concise, this really helps keep the code maintainable. Furthermore, this means that if we want to use line charts instead of points, we can reuse the axes. This is very effective when dealing with large projects with multiple chart types. It also makes it easy to test components individually, whether it is programmatically or manually tested in the Active Style Guide.
<code>{model.ready ? (</code> <code>{model.ready ? (</code> <axes xlabel="{xAxisLabels[model.selectedAxes]}" xticks="{model.getXAxis()}" ylabel="{yAxisLabels[model.selectedAxes]}" yticks="{model.getYAxis()}"></axes><points points="{model.getPoints()}"></points> ) : ( <loading></loading> )}
Try commenting out the axes and points components in the Sandbox above to see how they work independently.
Finally, we wrap the component in an observer function. This means that any changes in the model will trigger re-rendering.
<code>export default observer(HeartrateChart);</code>
Let's take a look at the Axes component:
As you can see, we have an XAxis and a YAxis. Each has a label and a set of tick marks. We'll cover how to create markers later, but here you should note that each axis consists of a set of ticks generated by traversing a set of objects with labels and x or y values (depending on which axis we're rendering).
Try changing some attribute values of the element to see what happens...or what breaks! For example, change the line element in YAxis to the following:
<code><line x1="{30}" x2="95%" y1="{0}" y2="{y}"></line></code>
The best way to learn how to build visualizations using SVG is to simply experiment and destroy things. ?
OK, that's half of the chart. Now let's look at the Points component.
Each point on the chart consists of two parts: an SVG image and a circular element. The image is an animal icon, and the circle provides an animation of impulsiveness that is visible when hovering the mouse over the icon.
Try commenting out the image element, then commenting out the circular element and see what happens.
This time the model must provide an array of point objects that provide us with four properties: the x and y values for the anchor points on the chart, the label of the points (the name of the animal), and the pulse, which is the duration of the pulse animation for each animal icon. Hopefully this all looks intuitive and logical.
Again, try modifying the property value to see which changes and interrupts are made. You can try setting the y property of the image to 0. Trust me, this is much easier than reading the W3C specification for SVG image elements!
Hope this gives you an idea of how we render charts in React. Now, you just need to create a model with appropriate operations to generate the data we need to loop through in JSX.
Create our memory
Here is the complete code for the memory:
I broke the code into the three parts mentioned above:
- Define the properties of the model
- Define the operation
- Define the view
Define the properties of the model
Everything we define here is accessible from external properties as a model instance, and – if observable wrapper components are used – any changes to these properties will trigger re-rendering.
<code>.model('ChartModel', { animals: types.array(AnimalModel), paddingAndMargins: types.frozen({ paddingX: 30, paddingRight: 0, marginX: 30, marginY: 30, marginTop: 30, chartHeight: 500 }), ready: false, // 表示types.boolean,默认为false selectedAxes: 0 // 表示types.number,默认为0 })</code>
Each animal has four data points: name (Creature), lifespan (Longevity__Years_), weight (Mass__grams_), and resting heart rate (Resting_Heart_Rate__BPM_).
<code>const AnimalModel = types.model('AnimalModel', { Creature: types.string, Longevity__Years_: types.number, Mass__grams_: types.number, Resting_Heart_Rate__BPM_: types.number });</code>
Define the operation
We only have two operations. The first (setSelectedAxes) is called when changing the drop-down menu, which updates the selectedAxes property, which in turn determines which data is used to render the axis.
<code>setSelectedAxes(val) { self.selectedAxes = val; },</code>
The setUpScales operation requires more explanation. This function is called in the useEffect hook function immediately after the chart component is mounted, or after the window is resized. It accepts an object containing the width of the DOM containing the element. This allows us to set a scale function for each axis to fill the full available width. I will explain the scale function soon.
To set the scale function we need to calculate the maximum values for each data type, so we first traverse the animals to calculate these maximum values and minimum values. We can use zeros as the minimum value of any scale we want to start from zero.
<code>// ... self.animals.forEach( ({ Creature, Longevity__Years_, Mass__grams_, Resting_Heart_Rate__BPM_, ...rest }) => { maxHeartrate = Math.max( maxHeartrate, parseInt(Resting_Heart_Rate__BPM_, 10) ); maxLongevity = Math.max( maxLongevity, parseInt(Longevity__Years_, 10) ); maxWeight = Math.max(maxWeight, parseInt(Mass__grams_, 10)); minWeight = minWeight === 0 ? parseInt(Mass__grams_, 10) : Math.min(minWeight, parseInt(Mass__grams_, 10)); } ); // ...</code>
Now set the scale function! Here we will use the scaleLinear and scaleLog functions of D3.js. When setting these functions we specify the domain, which is the minimum and maximum input that the function can expect, and the range, which is the maximum and minimum output.
For example, when I call self.heartScaleY with the maxHeartate value, the output will equal marginTop. This makes sense because this will be at the top of the chart. For the lifetime attribute, we need to have two scale functions, because this data will be displayed on the x-axis or y-axis, depending on which drop-down option is selected.
<code>self.heartScaleY = scaleLinear() .domain([maxHeartrate, minHeartrate]) .range([marginTop, chartHeight - marginY - marginTop]); self.longevityScaleX = scaleLinear() .domain([minLongevity, maxLongevity]) .range([paddingX marginY, width - marginX - paddingX - paddingRight]); self.longevityScaleY = scaleLinear() .domain([maxLongevity, minLongevity]) .range([marginTop, chartHeight - marginY - marginTop]); self.weightScaleX = scaleLog() .base(2) .domain([minWeight, maxWeight]) .range([paddingX marginY, width - marginX - paddingX - paddingRight]);</code>
Finally, we set self.ready to true because the chart is ready for rendering.
Define the view
We have two sets of view functions. The first set outputs the data required to render the axis scale (I said we'll get there!) the second set outputs the data required to render the point. We will first look at the scale function.
There are only two tick functions called from the React application: getXAxis and getYAxis. These functions simply return the output of other view functions based on the value of self.selectedAxes.
<code>getXAxis() { switch (self.selectedAxes) { case 0: return self.longevityXAxis; break; case 1: case 2: return self.weightXAxis; break; } }, getYAxis() { switch (self.selectedAxes) { case 0: case 1: return self.heartYAxis; break; case 2: return self.longevityYAxis; break; } },</code>
If we look at the Axis functions themselves, we can see that they use the ticks method of the scale function. This returns a set of numbers that fit the axis. We then iterate through these values to return the data required by the axis component.
<code>heartYAxis() { return self.heartScaleY.ticks(10).map(val => ({ label: val, y: self.heartScaleY(val) })); } // ...</code>
Try changing the parameter value of the ticks function to 5 and see how it affects the chart: self.heartScaleY.ticks(5).
Now we have a view function that returns the data required by the Points component.
If we look at longevityHeartratePoints (which returns the point data for the Lifespan vs Heart Rate graph), we can see that we are traversing the animal array and using the appropriate scale function to get the x and y positions of the points. For pulse properties, we use some mathematical methods to convert the number of beats per minute of the heart rate into a numerical value representing the duration of a single heartbeat in milliseconds.
<code>longevityHeartratePoints() { return self.animals.map( ({ Creature, Longevity__Years_, Resting_Heart_Rate__BPM_ }) => ({ y: self.heartScaleY(Resting_Heart_Rate__BPM_), x: self.longevityScaleX(Longevity__Years_), pulse: Math.round(1000 / (Resting_Heart_Rate__BPM_ / 60)), label: Creature }) ); },</code>
At the end of the store.js file, we need to create a Store model and instantiate it with the raw data of the animal object. It is common practice to attach all models to the parent Store model, and then access these models through the top-level provider as needed.
<code>const Store = types.model('Store', { chartModel: ChartModel }); const store = Store.create({ chartModel: { animals: data } }); export default store;</code>
That's it! Here is our demonstration:
This is by no means the only way to organize data in JSX to build charts, but I found it very efficient. I've built a custom chart library for large enterprise clients using this structure and stack in a real environment and was shocked by how well MST performed in this purpose. I hope you have the same experience!
The above is the detailed content of Making a Chart? Try Using Mobx State Tree to Power the Data. For more information, please follow other related articles on the PHP Chinese website!

For a while, iTunes was the big dog in podcasting, so if you linked "Subscribe to Podcast" to like:

We lost Opera when they went Chrome in 2013. Same deal with Edge when it also went Chrome earlier this year. Mike Taylor called these changes a "Decreasingly

From trashy clickbait sites to the most august of publications, share buttons have long been ubiquitous across the web. And yet it is arguable that these

In this week's roundup, Apple gets into web components, how Instagram is insta-loading scripts, and some food for thought for self-hosting critical resources.

When I was looking through the documentation of git commands, I noticed that many of them had an option for . I initially thought that this was just a

Sounds kind of like a hard problem doesn't it? We often don't have product shots in thousands of colors, such that we can flip out the with . Nor do we

I like when websites have a dark mode option. Dark mode makes web pages easier for me to read and helps my eyes feel more relaxed. Many websites, including

This is me looking at the HTML element for the first time. I've been aware of it for a while, but haven't taken it for a spin yet. It has some pretty cool and


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

VSCode Windows 64-bit Download
A free and powerful IDE editor launched by Microsoft

DVWA
Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

SublimeText3 Linux new version
SublimeText3 Linux latest version

Dreamweaver CS6
Visual web development tools

MantisBT
Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.