react 使用 svg_使用SVG和React.js构建日历热图

react 使用 svg

跳舞语法 (Dancing Syntax)

介绍 (Introduction)

In 2016, I had put my academic plans on hold after my first year and a half of college in order to take care of my younger sibling when our mother was unable to. Some really amazing friends of mine stepped in to help me out and eventually ended up adopting her (and effectively me too!).

2016年,大学一年半后,我暂停了学业计划,以照顾母亲无法负担的年轻同胞。 我的一些很棒的朋友介入帮助我,并最终收养了她(实际上也是我!)。

After we stabilized, I felt the urge to return learning something other than dance, and I eventually ended up attending Flatiron School. It had been about five years since I had been in a school-like environment, so managing stress and self-care was difficult. My struggles with my awareness around my self-care inspired me to make an app to help people manage their care better.

在我们稳定下来之后,我感到有回到学习舞蹈以外的冲动的渴望,最终我最终进入了Flatiron学校。 自从我进入类似学校的环境以来,已经过去了大约五年时间,所以很难控制压力和自我照顾。 我对自我保健意识的挣扎激发了我开发一款应用程序,以帮助人们更好地管理自己的护理的过程。

While pre-planning for my app, I reached out to my friends on Facebook and asked them what they would like and dislike out of a self-care app. I got over 60 responses in minutes. These responses helped to form some user stories for my app. One particular response was:

在为我的应用程序进行预计划时,我在Facebook上与我的朋友们进行了接触,问他们在自助应用程序中他们想要和不喜欢的东西。 我在几分钟内收到60多个回复。 这些回应有助于为我的应用形成一些用户故事。 一个特别的答复是:

It would be cool to have some sort of simple data visualization for how you feel every day so you could track it and learn when you need to take more time for yourself. Something as simple as a calendar you color code for feelings like yellow = anxious, etc.

拥有一些简单的数据可视化功能来了解您每天的感觉,这很酷,这样您就可以跟踪并了解何时需要花费更多的时间。 像日历这样简单的事情,您会用颜色为黄色=焦虑等颜色编码。

I thought this would be super cool, at the time of reading this suggestion, I didn’t quite know how I would do it, and I dreaded working with dates in programming, but I set out to accomplish it, so let’s do it!

我认为这太棒了,在阅读此建议时,我不知道该怎么做,而且我对在编程中使用日期感到恐惧,但是我着手去实现它,所以就去做吧!

Image for post

有趣的部分 (The Fun Part)

最有价值球员 (MVP)

In order to make this happen I know I needed to keep track of a few things:

为了做到这一点,我知道我需要跟踪一些事情:

  • A user can pick from a list of feelings

    用户可以从感觉列表中进行选择
  • Each feeling is mapped to a display color for that feeling

    每个感觉都映射到该感觉的显示颜色
  • A dynamic, functional calendar to display the colors of a feeling on the given day when the feeling was reported

    动态,实用的日历,用于在报告感觉的特定日期显示感觉的颜色

I decided that the minimum viable product, or “skateboard”, version of this would group my feelings into categories of “Needs Satisfied” and “Needs Unsatisfied” → then categories of parent words → then highly specific feeling words. I did it this way to support a user deepening their emotional granularity (read: specific understanding). For example, it’s more useful and specific to report that you felt empowered than confident. I did some research and came away with 217 feeling words to start with.

我认为,最低限度的可行产品或“滑板”版本会将我的感觉分为“需要的需求”和“需要的需求”类别→然后是父词类别→然后是非常具体的感觉词。 我这样做是为了支持用户加深他们的情感粒度 (阅读:特定的理解)。 例如,举报您觉得自己有能力而不是自信更有用和更具体。 我做了一些研究,得出了217个感叹词。

Image for post
A portion of the 217 words I collected
我收集的217个单词中的一部分

I also decided that since I had so many words, I would assign colors to only the two topmost level categories, satisfied and unsatisfied. This will keep things simple and I plan to do more research into better words and mapping colors to feelings in a future refactor.

我还决定,由于我有很多单词,我只将颜色分配给两个最上层的类别,即满意和不满意。 这将使事情变得简单,我计划在将来的重构中对更好的单词进行更多研究,并将颜色映射到感觉上。

环境 (Environment)

I’m using Ruby on Rails as a backend API with PostgreSQL as my database. I’m using React.js for my frontend client.

我将Ruby on Rails用作后端API,而将PostgreSQL作为数据库。 我正在为我的前端客户端使用React.js。

准备SVG (Preparing the SVG)

For my project, I had the great pleasure to work with two designers, Anna Wu and Kendra Jenel. They created this graphic for me:

对于我的项目,我非常高兴能与两位设计师Anna WuKendra Jenel合作 。 他们为我创建了以下图形:

Image for post
Ex. 1 Concept Calendar with colored squares to indicate feelings reported
例如 1个带有彩色正方形的概念日历,用于表示所报告的感受

After exporting it from Figma as SVG I edited the SVG to make the cells the same blank fill color and resized the first cell that highlights the current day in Ex. 1. Now it looks like this:

从Figma导出为SVG后,我编辑了SVG,使单元格具有相同的空白填充颜色,并调整了第一个突出显示Ex中当前日期的单元格的大小。 1.现在看起来像这样:

Image for post
Blank Concept Calendar
空白概念日历

From there I went to SVGtoJSX in order to quickly turn this SVG into a React Component. You have to do this because not all SVG properties are valid JSX. For example: stroke-width = valid SVG, strokeWidth = valid JSX.

从那里我去了SVGtoJSX ,以便快速地将此SVG变成一个React组件。 您必须这样做,因为并非所有SVG属性都是有效的JSX。 例如:stroke-width =有效的SVG,strokeWidth =有效的JSX。

SVGtoJSX also allows you to toggle the presence of IDs and determine whether you want a functional or class component, as well as memorize and toggle single quotes or double quotes.

SVGtoJSX还允许您切换ID的存在并确定是否要使用功能或类组件,还可以存储和切换单引号或双引号。

After all that, we have a React component to work with.

毕竟,我们有一个React组件可以使用。

Currently, the displayed month is an SVG <path> which means that instead of text, it is drawn as a series of attributes telling the client where, how, and what to draw.

当前,显示的月份是SVG <path> ,这意味着它以文本而不是文本的形式绘制,并通过一系列属性来告知客户绘制位置,方式和内容。

<path id="July" d="M160.477 32.18C159.673 32.18 158.929 32.024 158.245 31.712C157.573 31.388 157.021 30.938 156.589 30.362L157.921 28.76C158.617 29.72 159.451 30.2 160.423 30.2C161.731 30.2 162.385 29.426 162.385 27.878V21.362H157.903V19.4H164.725V27.752C164.725 29.228 164.365 30.338 163.645 31.082C162.925 31.814 161.869 32.18 160.477 32.18ZM177.097 22.388V32H174.955V30.776C174.595 31.208 174.145 31.544 173.605 31.784C173.065 32.012 172.483 32.126 171.859 32.126C170.575 32.126 169.561 31.772 168.817 31.064C168.085 30.344 167.719 29.282 167.719 27.878V22.388H169.969V27.572C169.969 28.436 170.161 29.084 170.545 29.516C170.941 29.936 171.499 30.146 172.219 30.146C173.023 30.146 173.659 29.9 174.127 29.408C174.607 28.904 174.847 28.184 174.847 27.248V22.388H177.097ZM180.043 18.644H182.293V32H180.043V18.644ZM194.247 22.388L189.747 32.774C189.327 33.818 188.817 34.55 188.217 34.97C187.617 35.402 186.891 35.618 186.039 35.618C185.559 35.618 185.085 35.54 184.617 35.384C184.149 35.228 183.765 35.012 183.465 34.736L184.365 33.08C184.581 33.284 184.833 33.446 185.121 33.566C185.421 33.686 185.721 33.746 186.021 33.746C186.417 33.746 186.741 33.644 186.993 33.44C187.257 33.236 187.497 32.894 187.713 32.414L187.875 32.036L183.681 22.388H186.021L189.045 29.498L192.087 22.388H194.247Z" fill="black"/>,

In order to control what month is displayed, I made an array of these paths and a function to render that month <path> based on state.

为了控制显示哪个月份,我制作了这些路径的数组以及一个根据状态呈现该月份<path>的函数。

Github gist showing the SVG Calendar Month paths
Github要点显示SVG日历月路径

Refactor point: I’ll likely come back later and make this an object instead with the current month as the key to grab the month path.

重构点:我很可能稍后再回来,使它成为一个对象,而不是将当前月份作为获取月份路径的关键。

Finally, I give all of my calendars’ SVG rectangles a date attribute set to "blank", then I wrap the <rect></rect> nodes into row groups that represent weeks. Then give each row <g></g> an id, and wrap the entire calendar in a group for the month, giving it an id and onClick event listener.

最后,将所有日历的SVG矩形的日期属性设置为"blank",然后将<rect></rect>节点包装到代表周的行组中。 然后为每行<g></g>一个ID,并将整个日历包装在该月的组中,并为其指定ID和onClick事件监听器。

Image for post

日历功能 (Calendar Functionality)

建立 (Setup)

This first step I took was to define some state to keep track of the current day, the current month, and the current year.

我采取的第一步是定义一些状态以跟踪当日,当月和当年。

Github gist showing some initial code setup
Github gist显示一些初始代码设置

In each function, I am defining a constant date and setting it equal to new Date() . According to MDN:

在每个函数中,我都定义一个常量date并将其设置为等于new Date() 。 根据MDN:

JavaScript Date objects represent a single moment in time in a platform-independent format. Date objects contain a Number that represents milliseconds since 1 January 1970 UTC.

JavaScript Date对象以独立于平台的格式表示单个时刻。 Date对象包含一个Number ,该Number表示自1970年1月1日UTC以来的毫秒Number

As I am writing this it is Wednesday, July 29th so calling new Date() with no parameters gives me a date object for today’s date and the time is the time at which I initiated the Date() call. It displays in a human-readable format showing the GMT offset and my local time zone.

在撰写本文时,它是7月29 new Date()星期三),因此调用不带参数的new Date()会给我一个日期对象,作为今天的日期,而时间就是我发起Date()调用的时间。 它以人类可读的格式显示,显示GMT偏移量和我的本地时区。

new Date()
//=> Wed Jul 29 2020 13:07:23 GMT-0700 (Pacific Daylight Time)

I’m using the toLocaleString method in the interest of future-proofing this for different timezones, but I’ll likely refactor that down the road.

我正在使用toLocaleString方法,以便将来针对不同时区进行验证,但是很可能会在将来进行重构。

The next step is to get both the first day of the given month and the number of days in a given month.

下一步是获取给定月份的第一天和给定月份的天数。

I found this blog post on Medium by Nitin Patel which helped me figure out how to get this data (thanks!). Patel is generating their entire calendar using raw CSS and JavaScript, which is super cool so check that out for the extra challenge.

我在Nitin Patel的 Medium上找到了这篇博客文章 ,这有助于我弄清楚如何获取此数据(谢谢!)。 Patel使用原始CSS和JavaScript生成了他们的整个日历,这非常酷,因此请查看其他挑战。

// 1. Get days in given month, for given year.       daysInMonth = (iMonth, iYear) => {
return 32 - new Date(iYear, iMonth, 32).getDate();
}a. new Date(2020, 0, 32)
//=> Sat Feb 01 2020 00:00:00 GMT-0800 (Pacific Standard Time)b. new Date(2020, 0, 32).getDate()
//=> 1c. 32 - 1
//=> 31//Jan 31st!// 2. Get first day in given month, for given year. let firstDay = (new Date(year, month)).getDay();a. new Date(2020, 0)
//=> Wed Jan 01 2020 00:00:00 GMT-0800 (Pacific Standard Time)b. (new Date(2020, 0)).getDay();
//=> 3//Wednesday!

In Function 1, new Date(iYear, iMonth, 32).getDate() returns the numerical day of the 32nd day after the first day of the passed in month. JavaScript counts months start from 0 and the method getDay() returns the numerical day of the week, also starting the count from 0. We subtract that number from 32 and arrive on the final day for the passed in month.

在函数1中, new Date(iYear, iMonth, 32).getDate()返回传入月份的第一天之后第32天的数字天。 JavaScript从0开始计数月份,方法getDay()返回星期几,也从0开始计数。我们从32中减去该数字,并在传入的月份的最后一天到达。

Note: all the functions for setting state and populating the calendar are called within componentDidMount .

注意:用于设置状态和填充日历的所有函数都在componentDidMount中调用。

componentDidMount(){this.setMonth()this.setYear()this.setCurrent()this.populateCalendar(new Date().getFullYear(), new Date().getMonth())}

Now that I have this initial setup completed, I can move on to populating the calendar with dates.

现在,我已经完成了初始设置,接下来可以继续使用日期填充日历了。

循环反循环! (Loop-de-loop!)

Now for the fun part: Loops!

现在开始有趣的部分:循环!

First, I add a ref to my constructor and attach that ref to the <g></g> that represents the month. React refs give us a way of referencing and accessing DOM nodes. You can access the attached node by calling .current on this.namedRef .

首先,我向构造函数添加一个引用,并将该引用附加到表示月份的<g></g>上。 React refs为我们提供了一种引用和访问DOM节点的方法。 您可以通过在this.namedRef上调用.current来访问附加的节点。

constructor(props){
super(props)
this.monthRef = React.createRef()
}...further down in file<g id="month" ref={this.monthRef} onClick={this.handleClick}>
...Week rows and Day Rectangles
</g>

Now, I’ll define my function that will take in a numerical year and month and define the variables I’ll need to start off my loopalooza. Here’s the code, and there’s a step by step following it.

现在,我将定义将采用数字年份和月份的函数,并定义开始循环循环所需的变量。 这是代码,后面是逐步的代码。

Github gist showing the code to loop over the calendar nodes.
Github要点显示了循环遍历日历节点的代码。

一步步 (Step By Step)

  1. Iterate with a for loop through the month, cal.current.children will return an HTMLCollection which is our rows representing weeks. Each step of the row iteration will bring us to a new week.

    在月份中使用for循环进行迭代, cal.current.children将返回HTMLCollection,这是我们代表周的行。 行迭代的每一步将带我们进入新的一周。

  2. Define a variable representing the current iteration week’s child day <rect></rect> .

    定义一个变量,该变量代表当前迭代周的子日<rect></rect>

  3. Iterate again through the week, define a variable to represent the current iteration day <rect></rect> .

    在一周中再次迭代,定义一个变量以表示当前迭代日<rect></rect>

  4. Now that we have iterated over the entire structure, we can start populating the date property on each <rect></rect>.

    现在我们遍历了整个结构,我们可以开始在每个<rect></rect>.上填充date属性<rect></rect>.

  5. Check if we are on the first iteration and the second iteration’s numerical value is less than the value of firstDay. If true, continue the iteration. This will leave the date property on each rectangle “blank” until we hit the proper day of the week to start assign values.

    检查我们是否在第一次迭代中,并且第二次迭代的数值小于firstDay的值。 如果为true,则继续迭代。 这将在每个矩形上将date属性保留为“空白”,直到我们点击一​​周中的适当日期开始分配值为止。

  6. Check if the numerical value of the date variable is greater than the returned numerical value of daysInMonth function. If true, we have completed assigning values and we can break from the loops. we will increment date outside our loops

    检查date变量的数值是否大于daysInMonth函数的返回数值。 如果为true,则我们已经完成了赋值,可以从循环中打破。 我们将在循环之外增加date

  7. Access the attributes of the currentCell at the current step of the nested loop and update the date value with the value from the date variable.

    在嵌套循环的当前步骤访问currentCell的属性,并使用date变量中的值更新日期值。

  8. Check if the value of date ,month , and year are equivalent to today's date in state, if true, then adjust the style of the currentCell to highlight the current day of the month. Since the highlight is a black outline around the rectangle, I have to decrease the size of the rectangle and draw the outline, which will shift my rectangle up 2.5 pixels and left 3 pixels. The last two lines in this block will reposition the cell where it should be properly respective to the other cells.

    检查datemonthyear是否等于状态中的今天的日期,如果为true,则调整currentCell的样式以突出显示该月的当前日期。 由于高光是矩形周围的黑色轮廓,因此我必须减小矩形的大小并绘制轮廓,这将使矩形向上移动2.5个像素,向左移动3个像素。 此块中的最后两行将重新放置该单元格,使其正确地与其他单元格相对应。

  9. For our next trick, the part that sends us on to greater things, we will define two variables to store a count of satisfied and unsatisfied category feelings and map over the User’s reported feelings.

    对于我们的下一个技巧(将我们带入更多事物的那一部分),我们将定义两个变量来存储满意和不满意的类别感觉的计数,并映射用户报告的感觉。
  10. Inside the map, define a variable that holds the feeling object that is the result of filtering the list of feelings for specific feeling ID that matches the UserFeeling’s “feeling_id”

    在地图内,定义一个变量,用于保存感觉对象,该对象是过滤与UserFeeling的“feeling_id”相匹配的特定感觉ID的感觉列表的结果

  11. Define a variable that reads the UserFeeling’s “created_at” attribute, and get the numerical day number for the date the record was created with .getUTCDate() .

    定义一个变量,该变量读取UserFeeling的“created_at”属性,并使用.getUTCDate()创建记录的日期的数字天数。

#11 is super important. In my development environment, PostgreSQL will store my “created_at” date as a representation of UTC (Coordinated Universal Time) in the format YYYY-MM-DD HH:MM:SS:MS, but on the JavaScript side, my record gets converted into an ISO8601 format string which looks like this: 2020–07–01T02:32:21.580Z

#11非常重要。 在我的开发环境中,PostgreSQL将以YYYY-MM-DD HH:MM:SS:MS的格式存储我的“ created_at”日期作为UTC(世界标准时间)的表示形式,但是在JavaScript方面,我的记录被转换为看起来像这样的ISO8601格式字符串: 2020–07–01T02:32:21.580Z

The “T” Separates the date from the time, the “Z” stands for “Zulu” and indicates the zone designation for the zero offset of UTC time. Check out this example.

“ T”将日期与时间分开,“ Z”代表“ Zulu”,并表示UTC时间零偏移的区域名称。 看看这个例子。

Rails/PostGresQL:Records "created_at" stored as UTC:2020-07-01 02:32:21.580076Parse in JS (local time is the default):
new Date(“2020-07-01 02:32:21.580076”)//=> Wed Jul 01 2020 02:32:21 GMT-0700 (Pacific Daylight Time)
The same Record's "created_at" on the JavaScript side in ISO8601 format:2020–07–01T02:32:21.580ZParse in JS:
new Date(“2020–07–01T02:32:21.580Z”)//=> Tue Jun 30 2020 19:32:21 GMT-0700 (Pacific Daylight Time)Uh oh! We've lost the offset and it thinks it's the previous daynew Date("2020-07-01T02:32:21.580Z").getDate()//=> 30Fixed!new Date("2020-07-01T02:32:21.580Z").getUTCDate()//=> 1

We need to specify that instead of assuming local time, we want to calculate dates based on UTC so calling .getUTCDate gives us the correct day that is agnostic from timezones.

我们需要指定而不是假设本地时间,而是要基于UTC计算日期,因此调用.getUTCDate为我们提供与时区无关的正确日期。

12. Now that our dates are properly parsed, we can check to see if the currentCell‘s freshly minted date value is equivalent to the “created_at” date for our User’s reported feelings.

12.现在我们的日期已经正确解析,我们可以检查一下currentCell的最新日期值是否等于用户报告的感受的“created_at”日期。

13. Use a Ternary to increment either the satisfied or unsatisfied counts for that current day

13.使用三进制递增当天的满意计数或不满意计数

14. Finally, we can run a check to choose the fill color of the currentCell based on comparing satisfiedCount and unsatisfiedCount. Let’s pop in a debugger and watch this baby work!

14.最后,我们可以根据比较satisfiedCount currentCellunsatisfiedCount currentCell进行检查以选择currentCell的填充颜色。 让我们跳入调试器,看看这个宝贝!

Image for post
Stepping through the iterations to watch the calendar populate.
逐步执行迭代以查看日历的填充。
Image for post

结论 (Conclusion)

WE MADE IT! This was really fun and challenging to build. I am particularly happy that I feel armed with a better understanding of how to work with dates in JavaScript. I have some refactors in my future, but I am pleased that it is working for now.

我们做到了! 这真的很有趣,而且构建起来很有挑战性。 我感到特别高兴的是,我对如何在JavaScript中处理日期有了更好的了解。 我将来会有一些重构,但是我很高兴它现在可以正常工作。

What is something you are proud of building? Any questions? Let me know in the comments!

您为建造什么感到骄傲? 任何问题? 在评论中让我知道!

  1. Date Constructor— MDN

    日期构造器— MDN

  2. ISO8601 — Wikipedia

    ISO8601 —维基百科

  3. Coordinated Universal Time — Wikipedia

    协调世界时—维基百科

  4. GMT vs. UTC — Time and Date

    GMT vs. UTC —时间和日期

  5. Challenge of building a Calendar with Pure JavaScript Nitin Patel

    使用纯JavaScript构建日历的挑战Nitin Patel

  6. SVGtoJSX

    SVGtoJSX

翻译自: https://medium.com/javascript-in-plain-english/building-a-calendar-heatmap-with-svg-and-react-js-6751b19a2d95

react 使用 svg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值