CSS شگفت‌انگیز است – من مرتباً از پیشرفت آن در سال‌هایی که از آن استفاده می‌کنم شگفت‌زده می‌شوم (~2005 – اکنون). یکی از این شگفتی‌ها زمانی رخ داد که متوجه این توییت شدم شروتی بالاسا که نحوه ایجاد نمودار دایره ای با استفاده از conic-gradient().

این نسبتاً ساده است. در اینجا یک قطعه کد آمده است:

div {
  background: conic-gradient(red 36deg, orange 36deg 170deg, yellow 170deg);
  border-radius: 50%;
}

با استفاده از این مقدار ناچیز CSS، می‌توانید شیب‌هایی ایجاد کنید که در زوایای خاصی شروع و متوقف می‌شوند و برای هر بخش از نمودار دایره‌ای یک رنگ تعریف کنید.

نمودارهای شیب مخروطی CSS با نمودارهای دونات و نمودار دایره ای
(پیش نمایش بزرگ)

روزهای خوش!

بریلز، فکر کردم می‌توانم از این به‌جای یک کتابخانه نمودار برای پروژه داشبورد داده‌ای که دارم روی آن کار می‌کنم استفاده کنم. CockroachDB Cloud API، اما من یک مشکل داشتم. من مقادیر نمودار خود را از قبل نمی دانستم و مقادیری که از API دریافت می کردم بر حسب درجه نبودند!

در اینجا یک پیوند پیش‌نمایش و مخزن منبع باز از نحوه کار من برای حل این دو مشکل وجود دارد، و در ادامه این پست، نحوه عملکرد همه آن‌ها را توضیح خواهم داد.

مقادیر دینامیک داده

در اینجا چند داده نمونه از a معمول پاسخ API که من بر اساس آن مرتب کرده ام value.

const data = [
  {
    name: 'Cluster 1',
    value: 210,
  },
  {
    name: 'Cluster 2',
    value: 30,
  },
  {
    name: 'Cluster 3',
    value: 180,
  },
  {
    name: 'Cluster 4',
    value: 260,
  },
  {
    name: 'Cluster 5',
    value: 60,
  },
].sort((a, b) => a.value - b.value);

می توانید ببینید که هر آیتم در آرایه دارای یک است name و الف value.

به منظور تبدیل value از یک عدد به a deg ارزش مورد استفاده در CSS، چند کار وجود دارد که باید انجام دهید:

  • مقدار کل همه مقادیر را محاسبه کنید.
  • از مقدار کل برای محاسبه درصدی که هر مقدار نشان می دهد استفاده کنید.
  • درصد را به درجه تبدیل کنید.

توجه داشته باشید: کدی که در مراحل زیر به آن اشاره خواهم کرد را می توانید در مخزن اینجا پیدا کنید: /components/donut-1.js.

بیشتر بعد از پرش! ادامه مطلب زیر ↓

مجموع مبلغ را محاسبه کنید

با استفاده از جاوا اسکریپت، می توانید از این تک خط کوچک استفاده کنید مجموع هر مقدار را از آرایه داده افزایش دهید، که منجر به یک کل واحد می شود.

const total_value = data.reduce((a, b) => a + b.value, 0);

// => 740

محاسبه درصد

حالا که شما یک total_value، می توانید هر یک از مقادیر آرایه داده را با استفاده از یک تابع جاوا اسکریپت به درصد تبدیل کنید. من این تابع را صدا زدم covertToPercent.

توجه داشته باشید: من در این مثال از مقدار 210 از Cluster 1 استفاده کرده ام.

const convertToPercent = (num) => Math.round((num / total_value) * 100);

// convertToPercent(210) => 28

تبدیل درصد به درجه

هنگامی که یک درصد دارید، می توانید با استفاده از یک تابع جاوا اسکریپت، درصد را به درجه تبدیل کنید. من این تابع را صدا زدم convertToDegrees.

const convertToDegrees = (num) => Math.round((num / 100) * 360);

// convertToDegrees(28) => 101

نتیجه

به عنوان یک آزمایش موقت، اگر قرار باشد نقشه بر روی آیتم های موجود در آرایه داده مرتب شده، با استفاده از دو تابع توضیح داده شده در بالا، خروجی زیر را خواهید داشت:

const test_output = data.map((item) => {
  const percentage = convertToPercent(item.value);
  const degrees = convertToDegrees(percentage);

  return `${degrees}deg`;
});

// => ['14deg', '29deg', '86deg', '101deg', '126deg']

مقدار بازگشتی از test_output آرایه ای از value (در درجه) + رشته deg.

این یکی از مشکلات دو قسمتی را حل می کند. حالا قسمت دیگر مشکل را توضیح می دهم.

برای ایجاد نمودار دایره ای با استفاده از conic-gradient()، شما به دو مورد نیاز دارید deg ارزش های. اولی زاویه ای است که شیب باید از آنجا شروع شود و دومی زاویه ای است که گرادیان باید متوقف شود. شما همچنین به یک رنگ برای هر بخش نیاز دارید، اما من در یک لحظه به آن خواهم رسید.

 ['red 🤷 14deg', 'blue 🤷 29deg', 'green 🤷 86deg', 'orange 🤷 101deg', 'pink 🤷 126deg']

با استفاده از مقادیر از test_output، من فقط مقدار پایانی را دارم (جایی که گرادیان باید متوقف شود). زاویه شروع برای هر بخش در واقع زاویه پایانی از آیتم قبلی در آرایه است و زاویه پایانی مقدار تجمعی تمام مقادیر پایانی قبلی به اضافه مقدار پایان فعلی است. و برای بدتر شدن اوضاع، مقدار شروع برای اولین زاویه باید به صورت دستی تنظیم شود 0 🥴.

در اینجا یک نمودار برای توضیح بهتر معنی آن وجود دارد:

نموداری که یک تابع را توضیح می دهد
(پیش نمایش بزرگ)

اگر گیج کننده به نظر می رسد، به این دلیل است که اینطور است، اما اگر به خروجی تابعی نگاه کنید که می تواند همه این کارها را انجام دهد، ممکن است منطقی تر باشد.

"#...", 0, 14,
"#...",, 14, 43,
"#...",, 43, 130,
"#...",, 130, 234,
"#...",, 234, 360,

عملکردی که می تواند همه اینها را انجام دهد

و در اینجا تابعی است که واقعاً می تواند همه اینها را انجام دهد. استفاده می کند reduce() برای تکرار روی آرایه داده، جمع لازم را برای محاسبه زوایا انجام می‌دهد و مجموعه جدیدی از اعداد را برمی‌گرداند که می‌توان از آنها برای ایجاد زوایای شروع و پایان صحیح برای استفاده در نمودار استفاده کرد.

const total_value = data.reduce((a, b) => a + b.value, 0);
const convertToPercent = (num) => Math.round((num / total_value) * 100);
const convertToDegrees = (num) => Math.round((num / 100) * 360);

const css_string = data
  .reduce((items, item, index, array) => {
    items.push(item);

    item.count = item.count || 0;
    item.count += array[index - 1]?.count || item.count;
    item.start_value = array[index - 1]?.count ? array[index - 1].count : 0;
    item.end_value = item.count += item.value;
    item.start_percent = convertToPercent(item.start_value);
    item.end_percent = convertToPercent(item.end_value);
    item.start_degrees = convertToDegrees(item.start_percent);
    item.end_degrees = convertToDegrees(item.end_percent);

    return items;
  }, [])
  .map((chart) => {
    const { color, start_degrees, end_degrees } = chart;
    return ` ${color} ${start_degrees}deg ${end_degrees}deg`;
  })
  .join();

من عمداً این را بسیار پرمخاطب گذاشته‌ام، بنابراین اضافه کردن آن آسان‌تر است console.log(). زمانی که این تابع را توسعه می‌دادم، متوجه شدم که این بسیار مفید است.

ممکن است متوجه موارد اضافی شوید map زنجیر شده تا انتهای reduce. با استفاده از a map من می توانم مقادیر بازگشتی را تغییر دهم و روی آن تثبیت کنم deg، سپس همه آنها را با هم به عنوان یک آرایه از رشته ها برگردانید.

استفاده كردن join درست در انتها آرایه را به یک واحد تبدیل می کند css_string، که می توان با آن استفاده کرد conic-gradient() 😅

"#..." 0deg 14deg,
"#..." 14deg 43deg,
"#..." 43deg 130deg,
"#..." 130deg 234deg,
"#..." 234deg 360deg

با استفاده از css_string با یک SVG foreignObject

الان متاسفانه نمیتونی استفاده کنی conic-gradient() با SVG. اما می توانید یک عنصر HTML را در داخل a قرار دهید foreignObject و سبک background با استفاده از a conic-gradient().

<svg viewBox='0 0 100 100' xmlns="http://www.w3.org/2000/svg" style={{ borderRadius: '100%' }}>
  <foreignObject x='0' y='0' width="100" height="100">
    <div
      xmlns="http://www.w3.org/1999/xhtml"
      style={{
        width: '100%',
        height: '100%',
        background: `conic-gradient(${css_string})`, // <- 🥳
      }}
    />
  </foreignObject>
</svg>

با استفاده از موارد فوق، باید به نمودار دایره ای نگاه کنید. برای ایجاد نمودار دونات، باید نحوه ایجاد سوراخ را توضیح دهم.

بیایید در مورد سوراخ صحبت کنیم

واقعاً تنها یک راه وجود دارد که می‌توانید از وسط نمودار دایره «ماسک» کنید تا پس‌زمینه را آشکار کنید. این رویکرد شامل استفاده از a clipPath. این رویکرد شبیه قطعه کد زیر است. من از این برای Donut 1 استفاده کردم.

توجه داشته باشید: را src برای Donut 1 را می توان در اینجا مشاهده کرد: components/donut-1.js.

<svg viewBox='0 0 100 100' xmlns="http://www.w3.org/2000/svg" style={{ borderRadius: '100%' }}>

  <clipPath id='hole'>
    <path d='M 50 0 a 50 50 0 0 1 0 100 50 50 0 0 1 0 -100 v 18 a 2 2 0 0 0 0 64 2 2 0 0 0 0 -64' />
  </clipPath>

  <foreignObject x='0' y='0' width="100" height="100" clipPath="url(#hole)">
    <div
      xmlns="http://www.w3.org/1999/xhtml"
      style={{
        width: '100%',
        height: '100%',
        background: `conic-gradient(${css_string})`
      }}
    />
  </foreignObject>
</svg>

با این حال، راه دیگری وجود دارد. این رویکرد شامل استفاده از a <circle /> عنصر و قرار دادن آن در مرکز نمودار دایره ای. این کار خواهد کرد اگر پر از <circle /> با رنگ پس‌زمینه هر چیزی که نمودار روی آن قرار می‌گیرد مطابقت دارد. در مثالم، من از یک پس‌زمینه الگو استفاده کرده‌ام، و اگر به Donut 3 دقت کنید متوجه خواهید شد که نمی‌توانید آن را ببینید. الگوی حباب از مرکز نمودار

توجه داشته باشید: را src برای Donut 3 را می توانید در اینجا ببینید: components/donut-3.js.

<svg viewBox='0 0 100 100' xmlns="http://www.w3.org/2000/svg" style={{ borderRadius: '100%' }}>
  <foreignObject x='0' y='0' width="100" height="100">
    <div
      xmlns="http://www.w3.org/1999/xhtml"
      style={{
        width: '100%',
        height: '100%',
        background: `conic-gradient(${css_string})`
      }}
    />
  </foreignObject>
  <circle cx='50' cy='50' r="32" fill="white" />
</svg>

IMO clipPath روش زیباتر است، اما اگر به چیزی مانند Figma یا Illustrator دسترسی ندارید، اصلاح نقاط مسیر برای به دست آوردن ضخامت مورد نظر سوراخ می تواند دشوارتر باشد.

بالاخره رنگ ها!

رنگ ها برای نمودار چیزی هستند که همیشه برای من مشکل ایجاد می کنند. بیشتر اوقات، رنگ هایی که من استفاده می کنم در CSS تعریف می شوند و همه این موارد در جاوا اسکریپت اتفاق می افتد، بنابراین چگونه از متغیرهای CSS در جاوا اسکریپت استفاده می کنید؟

در سایت نمونه خود، من از آن استفاده می کنم باد دم به سبک “همه چیزها” و با استفاده از این ترفند، من می توانم متغیرهای CSS را در معرض نمایش قرار دهم تا بتوان با نام آنها به آنها اشاره کرد.

اگر می خواهید همین کار را انجام دهید، می توانید یک را اضافه کنید color کلید آرایه داده:

data={[
  {
    name: 'Cluster 1',
    value: 210,
    color: 'var(--color-fuchsia-400)',
  },
  {
    name: 'Cluster 2',
    value: 30,
    color: 'var(--color-fuchsia-100)',
  },
  {
    name: 'Cluster 3',
    value: 180,
    color: 'var(--color-fuchsia-300)',
  },
  {
    name: 'Cluster 4',
    value: 260,
    color: 'var(--color-fuchsia-500)',
  },
  {
    name: 'Cluster 5',
    value: 60,
    color: 'var(--color-fuchsia-200)',
  },
].sort((a, b) => a.value - b.value)

و سپس ارجاع دهید color کلید در آرایه map آن را به عنوان بخشی از css_string. من از این روش در Donut 2 استفاده کرده ام.

توجه داشته باشید: می توانی ببینی src برای دونات 2 اینجا: components/donut-2.js.

.map((chart) => {
  const { color, start_degrees, end_degrees } = chart;
  return ` ${color} ${start_degrees}deg ${end_degrees}deg`;
})
.join();

شما حتی می توانید به صورت پویا نام رنگ را با استفاده از یک مقدار رمزگذاری شده ایجاد کنید (color-pink-) + the index از آرایه من از این روش در Donut 1 استفاده کرده ام.

توجه داشته باشید: می توانی ببینی src برای دونات 1 اینجا: components/donut-1.js.

.map((chart, index) => {
  const { start_degrees, end_degrees } = chart;
  return ` var(--color-pink-${(index + 1) * 100}) ${start_degrees}deg ${end_degrees}deg`;
})
.join();

اگر خوش شانس باشی!

با این حال، ممکن است خوش شانس باشید و با یک API کار کنید که در واقع مقادیر را با یک رنگ مرتبط برمی گرداند. این مورد در مورد است GitHub GraphQL API. بنابراین. آخرین نمونه را با هم جمع کردم.

GitHub GraphQL API با نمودار Github با ده زبان مختلف مرتبط با رنگ خود
(پیش نمایش بزرگ)

با مراجعه به این سایت می توانید این کار را در مرورگر خود مشاهده کنید /github، و src هم برای نمودار دونات GitHub و هم Legend را می توانید در اینجا پیدا کنید:

بسته بندی

ممکن است فکر کنید این بسیار پیچیده است، و احتمالاً استفاده از کتابخانه نمودار آسان‌تر است، و احتمالاً حق با شماست. احتمالا اینطور است. اما این راه است فوق العاده سبک وزن. هیچ کتابخانه اضافی برای نصب یا نگهداری وجود ندارد، و هیچ جاوا اسکریپت سنگینی وجود ندارد که باید توسط مرورگر دانلود شود تا بتواند کار کند.

من قبلاً یک بار با ایجاد نمودارهای Donut با استفاده از SVG و stroke-dashoffset. شما می توانید در مورد آن در مقاله من بخوانید، “یک نمودار دونات SVG از ابتدا برای وبلاگ گتسبی خود ایجاد کنید” این رویکرد واقعاً خوب کار کرد، اما فکر می‌کنم رویکردی را که در این پست توضیح داده شده ترجیح می‌دهم. CSS به سادگی بهترین است!

اگر می‌خواهید درباره هر یک از روش‌هایی که من در اینجا استفاده کرده‌ام بحث کنید، لطفاً مرا در توییتر پیدا کنید: @PaulieScanlon.

شما را در سراسر اینترنت می بینم!

سرمقاله Smashing
(yk, il)