D3.js (أو Data-Driven Documents) هي مكتبة JavaScript قوية لتصور البيانات وإنشاء رسومات تفاعلية في متصفح الويب. تتيح للمطورين إنشاء تصورات بيانية معقدة وتفاعلية باستخدام معايير الويب الحديثة مثل SVG وHTML5 وCSS.
أهم ما يميز D3 هو نهجها المرن الذي يتيح التحكم الكامل في المخرجات المرئية، على عكس العديد من مكتبات الرسوم البيانية الأخرى التي تقدم مخططات وقوالب محددة مسبقًا. تسمح D3 للمطورين ببناء تصورات بيانية مخصصة تمامًا حسب احتياجاتهم.
يمكنك تضمين D3.js في مشروعك بطرق مختلفة:
<!-- آخر إصدار (v7) -->
<script src="https://d3js.org/d3.v7.min.js"></script>
هذا السطر يقوم بتضمين آخر إصدار من D3.js مباشرة من شبكة توصيل المحتوى، مما يتيح استخدام D3 مباشرة في صفحتك دون الحاجة لتنزيل أو تجهيز ملفات إضافية.
npm install d3
ثم استيراد D3 في ملفات JavaScript الخاصة بك:
import * as d3 from 'd3';
// أو استيراد وحدات محددة فقط
import { select, selectAll } from 'd3';
هذه الطريقة مناسبة للمشاريع التي تستخدم أدوات بناء مثل Webpack أو Rollup، حيث تتيح استيراد المكتبة كاملة أو أجزاء محددة منها فقط لتقليل حجم الملف النهائي.
يمكنك تنزيل ملفات D3.js من صفحة الإصدارات وتضمينها مباشرة:
<script src="path/to/d3.min.js"></script>
هذه الطريقة مناسبة للبيئات التي قد لا تتوفر فيها اتصال إنترنت دائم، حيث يمكن تضمين الملفات محليًا.
لنبدأ بمثال بسيط لإنشاء رسم دائري:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>مثال D3.js الأول</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<div id="visualization"></div>
<script>
// إنشاء عنصر SVG
const svg = d3.select("#visualization")
.append("svg")
.attr("width", 500)
.attr("height", 300);
// إنشاء دائرة
svg.append("circle")
.attr("cx", 250)
.attr("cy", 150)
.attr("r", 100)
.attr("fill", "steelblue");
</script>
</body>
</html>
script src="https://d3js.org/d3.v7.min.js"
- تضمين مكتبة D3.js في المستندdiv id="visualization"
- إنشاء حاوية للرسم البيانيd3.select("#visualization")
- تحديد عنصر DOM باستخدام D3append("svg")
- إضافة عنصر SVG (رسم متجهي) إلى العنصر المحددattr("width", 500)
- تعيين عرض عنصر SVGattr("height", 300)
- تعيين ارتفاع عنصر SVGsvg.append("circle")
- إضافة دائرة إلى عنصر SVGattr("cx", 250)
- تعيين إحداثي x لمركز الدائرةattr("cy", 150)
- تعيين إحداثي y لمركز الدائرةattr("r", 100)
- تعيين نصف قطر الدائرةattr("fill", "steelblue")
- تعيين لون ملء الدائرةواحدة من أهم وظائف D3.js هي القدرة على تحديد وتلاعب بعناصر DOM. تعتبر التحديدات (Selections) حجر الأساس لمعظم عمليات D3.
// تحديد عنصر واحد باستخدام محدد CSS
const paragraph = d3.select("p");
// تحديد جميع العناصر المطابقة باستخدام محدد CSS
const allParagraphs = d3.selectAll("p");
// تحديد عنصر باستخدام معرف (ID)
const chart = d3.select("#chart");
// تحديد عناصر باستخدام فئة (Class)
const bars = d3.selectAll(".bar");
// تحديد باستخدام سمة (Attribute)
const inputs = d3.selectAll("input[type=checkbox]");
d3.select(selector)
- يحدد أول عنصر مطابق لمحدد CSS المعطى.d3.selectAll(selector)
- يحدد جميع العناصر المطابقة لمحدد CSS المعطى.بعد تحديد العناصر، يمكنك استخدام مجموعة متنوعة من الطرق للتعامل معها:
// تعديل نص العنصر
d3.select("#header").text("عنوان جديد");
// تعديل سمات العنصر
d3.select("circle")
.attr("r", 30) // تغيير نصف القطر
.attr("fill", "red") // تغيير اللون
.attr("stroke", "black") // إضافة حدود
.attr("stroke-width", 2); // تعيين عرض الحدود
// تعديل نمط CSS
d3.selectAll(".item")
.style("color", "blue")
.style("font-size", "14px")
.style("opacity", 0.8);
// إضافة وحذف الفئات (Classes)
d3.select("div")
.classed("active", true) // إضافة فئة
.classed("hidden", false); // إزالة فئة
// استخدام الدوال كقيم للسمات
d3.selectAll("circle")
.attr("cx", function(d, i) {
return i * 100 + 50; // تحديد الموضع بناءً على فهرس العنصر
});
.text(value)
- تعيين أو تغيير محتوى النص للعناصر المحددة..attr(name, value)
- تعيين أو تغيير قيمة سمة معينة للعناصر المحددة..style(name, value)
- تعيين أو تغيير خاصية CSS للعناصر المحددة..classed(name, boolean)
- إضافة أو إزالة فئة CSS من العناصر المحددة..property(name, value)
- تعيين خصائص DOM مثل checked و value.// إنشاء عناصر جديدة
d3.select("#container")
.append("div") // إضافة عنصر div
.attr("class", "new-item")
.text("عنصر جديد");
// إدراج عناصر قبل عناصر أخرى
d3.select("#list")
.insert("li", ":first-child") // إدراج قبل العنصر الأول
.text("عنصر في البداية");
// نسخ عناصر
d3.select(".template")
.clone(true) // نسخ العنصر مع الأحداث المرتبطة به
.attr("class", "copy");
// حذف عناصر
d3.select(".temp").remove(); // حذف العنصر من DOM
.append(type)
- إضافة عنصر جديد كآخر عنصر ابن للعناصر المحددة..insert(type, before)
- إدراج عنصر جديد قبل عنصر محدد..clone(deep)
- نسخ العناصر المحددة (deep تحدد ما إذا كان سيتم نسخ الأحداث والبيانات)..remove()
- إزالة العناصر المحددة من DOM.ميزة أساسية في D3 هي إمكانية تسلسل الطرق، حيث تعيد معظم طرق التحديد كائن التحديد نفسه:
// سلسلة من العمليات على تحديد واحد
d3.select("#chart")
.append("svg")
.attr("width", 600)
.attr("height", 400)
.append("rect")
.attr("x", 50)
.attr("y", 50)
.attr("width", 200)
.attr("height", 100)
.attr("fill", "orange")
.attr("stroke", "black")
.attr("stroke-width", 2);
عند استخدام .append()
أو طرق مشابهة، يتغير سياق التحديد الحالي. في المثال أعلاه:
#chart
.append("svg")
، يصبح التحديد هو عنصر SVG الجديد.append("rect")
، يصبح التحديد هو عنصر Rectangle الجديدربط البيانات هو جوهر D3.js. يتيح لك ربط مجموعة من البيانات بعناصر DOM، ثم التلاعب بخصائص هذه العناصر استنادًا إلى البيانات المرتبطة بها.
// مجموعة بيانات بسيطة
const data = [5, 10, 15, 20, 25];
// ربط البيانات بعناصر
d3.selectAll("p")
.data(data)
.text(function(d) {
return "القيمة هي: " + d;
});
.data(values)
- يربط مصفوفة البيانات المعطاة بالعناصر المحددة.d
في دالة.i
تمثل فهرس العنصر في المجموعة.أحد المفاهيم الأساسية في D3 هو نمط Enter/Update/Exit. يتيح لك التعامل بشكل مختلف مع الحالات التالية:
// مجموعة بيانات
const data = [10, 20, 30, 40, 50];
// تحديد الحاوية
const container = d3.select("#container");
// ربط البيانات بعناصر div حالية أو محتملة
const selection = container.selectAll("div")
.data(data);
// Enter: إنشاء عناصر جديدة للبيانات التي ليس لها عناصر مقابلة
selection.enter()
.append("div")
.attr("class", "bar")
.style("height", d => d + "px")
.style("width", "50px")
.style("background-color", "steelblue")
.style("margin", "5px")
.text(d => d);
// Update: تحديث العناصر الموجودة
selection
.style("background-color", "orange")
.text(d => "قيمة محدثة: " + d);
// Exit: إزالة العناصر الزائدة
selection.exit()
.style("background-color", "red")
.transition()
.duration(500)
.style("opacity", 0)
.remove();
.enter()
- يعيد تحديدًا يمثل العناصر التي يجب إنشاؤها..exit()
- يعيد تحديدًا يمثل العناصر التي يجب إزالتها..data()
) يمثل العناصر التي تحتاج إلى تحديث.// البيانات
const data = [50, 80, 120, 160, 200, 140, 90];
// إنشاء عنصر SVG
const svg = d3.select("#chart")
.append("svg")
.attr("width", 500)
.attr("height", 300);
// تحديد خصائص الرسم البياني
const margin = {top: 20, right: 20, bottom: 30, left: 40};
const width = 500 - margin.left - margin.right;
const height = 300 - margin.top - margin.bottom;
// إنشاء مجموعة (g) مع هامش
const g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// إنشاء مقياس x (المجال والنطاق)
const x = d3.scaleBand()
.domain(d3.range(data.length))
.range([0, width])
.padding(0.1);
// إنشاء مقياس y
const y = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([height, 0]);
// إنشاء الأعمدة
g.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", (d, i) => x(i))
.attr("y", d => y(d))
.attr("width", x.bandwidth())
.attr("height", d => height - y(d))
.attr("fill", "steelblue");
المقاييس هي دوال تحويل تساعد في تحويل البيانات إلى قيم مرئية (مثل الإحداثيات أو الألوان). تعتبر أساسية في D3 لأنها تساعد في تمثيل البيانات بشكل مرئي مناسب.
// مقياس خطي
const linearScale = d3.scaleLinear()
.domain([0, 100]) // مجال البيانات الأصلي (المدخلات)
.range([0, 500]); // نطاق القيم الناتجة (المخرجات)
// استخدام المقياس
console.log(linearScale(50)); // ناتج: 250
// مقياس فئوي (للقيم المنفصلة)
const bandScale = d3.scaleBand()
.domain(['A', 'B', 'C', 'D'])
.range([0, 400])
.padding(0.1); // تحديد المساحة بين الفئات
// مقياس زمني
const timeScale = d3.scaleTime()
.domain([new Date(2022, 0, 1), new Date(2022, 11, 31)])
.range([0, 800]);
// مقياس لوني
const colorScale = d3.scaleLinear()
.domain([0, 50, 100])
.range(["blue", "purple", "red"]);
// مقياس لوني متدرج
const categoryScale = d3.scaleOrdinal()
.domain(['فئة1', 'فئة2', 'فئة3', 'فئة4', 'فئة5'])
.range(d3.schemeCategory10);
scaleLinear
- يخطط قيم من مجال خطي إلى نطاق خطي. مناسب للبيانات العددية المستمرة.scaleBand
- يقسم النطاق إلى نطاقات متساوية. مفيد للرسوم البيانية ذات الأعمدة.scaleTime
- مقياس خطي مخصص للتعامل مع البيانات الزمنية.scaleOrdinal
- يخطط القيم الفئوية إلى نطاق محدد. مناسب للبيانات الاسمية.scaleQuantize
- يقسم المجال المستمر إلى نطاقات منفصلة.// تعيين النطاق يدويًا
const scale1 = d3.scaleLinear()
.domain([0, 100])
.range([0, 500]);
// استخدام الحدود الدنيا والقصوى للبيانات
const data = [12, 31, 45, 63, 28, 59];
const scale2 = d3.scaleLinear()
.domain([0, d3.max(data)]) // من 0 إلى القيمة القصوى
.range([0, 500]);
// استخدام الحدود الفعلية للبيانات
const scale3 = d3.scaleLinear()
.domain([d3.min(data), d3.max(data)])
.range([0, 500]);
// تعيين نطاق بالأبعاد المحددة للرسم البياني (مع هوامش)
const margin = {top: 20, right: 20, bottom: 30, left: 40};
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
// مقياس لمحور X (أفقي)
const xScale = d3.scaleBand()
.domain(d3.range(data.length))
.range([0, width])
.padding(0.1);
// مقياس لمحور Y (رأسي)
const yScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([height, 0]); // ملاحظة: النطاق معكوس (من ارتفاع إلى 0)
.nice()
لتقريب حدود النطاق إلى قيم "لطيفة" سهلة القراءة.[height, 0]
) لأن إحداثيات SVG تبدأ من الأعلى..clamp(true)
لتحديد القيم التي تقع خارج النطاق.المحاور تساعد في قراءة وتفسير الرسوم البيانية من خلال إضافة علامات ومسميات توضيحية. في D3، المحاور هي دوال تقوم برسم ملصقات ومؤشرات على طول مقياس معين.
// إنشاء مقاييس
const x = d3.scaleLinear()
.domain([0, 100])
.range([0, 400]);
const y = d3.scaleLinear()
.domain([0, 500])
.range([300, 0]);
// إنشاء محاور
const xAxis = d3.axisBottom(x); // محور أفقي في الأسفل
const yAxis = d3.axisLeft(y); // محور رأسي على اليسار
// إضافة المحاور إلى SVG
const svg = d3.select("#chart")
.append("svg")
.attr("width", 500)
.attr("height", 400);
// إضافة مجموعة للمحور الأفقي وتحديد موضعه
svg.append("g")
.attr("transform", "translate(50, 350)") // الهامش الأيسر، الارتفاع - الهامش السفلي
.call(xAxis); // تنفيذ الدالة على التحديد
// إضافة مجموعة للمحور الرأسي وتحديد موضعه
svg.append("g")
.attr("transform", "translate(50, 50)") // الهامش الأيسر، الهامش العلوي
.call(yAxis);
d3.axisBottom(scale)
- ينشئ محورًا أفقيًا مع تسميات في الأسفل.d3.axisTop(scale)
- ينشئ محورًا أفقيًا مع تسميات في الأعلى.d3.axisLeft(scale)
- ينشئ محورًا رأسيًا مع تسميات على اليسار.d3.axisRight(scale)
- ينشئ محورًا رأسيًا مع تسميات على اليمين..call(axis)
- طريقة مختصرة لتطبيق دالة على تحديد.// تخصيص عدد العلامات المرجعية
const xAxis = d3.axisBottom(x)
.ticks(5); // عدد العلامات التقريبي
// تخصيص تنسيق القيم
const yAxis = d3.axisLeft(y)
.tickFormat(d => d + "%"); // إضافة % إلى القيم
// تخصيص حجم العلامات
const xAxis2 = d3.axisBottom(x)
.tickSize(10) // حجم العلامات الصغيرة
.tickSizeOuter(15); // حجم العلامات الخارجية
// تحديد علامات معينة
const yAxis2 = d3.axisLeft(y)
.tickValues([0, 100, 250, 400, 500]); // علامات بقيم محددة
// إخفاء الخط الرئيسي للمحور
svg.append("g")
.call(xAxis)
.select(".domain") // تحديد الخط الرئيسي (domain)
.remove(); // إزالته
// تطبيق نمط CSS على المحور
svg.append("g")
.call(yAxis)
.attr("font-size", "12px")
.attr("font-family", "sans-serif");
.ticks(count)
- يحدد العدد المستهدف للعلامات (قد لا يكون دقيقًا)..tickFormat(format)
- يحدد تنسيق النص للعلامات..tickSize(size)
- يحدد حجم العلامات الداخلية والخارجية..tickSizeInner(size)
و .tickSizeOuter(size)
- يحددان حجم العلامات الداخلية والخارجية بشكل منفصل..tickPadding(padding)
- يحدد المسافة بين العلامات والنص..tickValues([values])
- يحدد قيمًا محددة للعلامات.// إضافة عنوان للمحور X
svg.append("text")
.attr("x", width / 2)
.attr("y", height + margin.bottom - 5)
.attr("text-anchor", "middle")
.text("الفترة الزمنية (بالأيام)");
// إضافة عنوان للمحور Y
svg.append("text")
.attr("transform", "rotate(-90)") // تدوير النص
.attr("x", -height / 2) // ملاحظة: بعد التدوير، تبدل x و y
.attr("y", -margin.left + 15)
.attr("text-anchor", "middle")
.text("القيمة (بالدولار)");
توفر D3.js مجموعة متنوعة من الدوال لإنشاء أشكال ورسومات في SVG، من الأشكال البسيطة إلى المسارات المعقدة.
// إنشاء عنصر SVG
const svg = d3.select("#chart")
.append("svg")
.attr("width", 500)
.attr("height", 300);
// إضافة مستطيل
svg.append("rect")
.attr("x", 50)
.attr("y", 50)
.attr("width", 200)
.attr("height", 100)
.attr("fill", "steelblue")
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("rx", 10) // نصف قطر زوايا المستطيل
.attr("ry", 10); // تدوير الزوايا
// إضافة دائرة
svg.append("circle")
.attr("cx", 350) // مركز الدائرة x
.attr("cy", 100) // مركز الدائرة y
.attr("r", 50) // نصف القطر
.attr("fill", "coral");
// إضافة خط
svg.append("line")
.attr("x1", 50) // نقطة البداية x
.attr("y1", 200) // نقطة البداية y
.attr("x2", 450) // نقطة النهاية x
.attr("y2", 200) // نقطة النهاية y
.attr("stroke", "green")
.attr("stroke-width", 3)
.attr("stroke-dasharray", "5,5"); // خط متقطع
// إضافة شكل بيضاوي
svg.append("ellipse")
.attr("cx", 350)
.attr("cy", 220)
.attr("rx", 100) // نصف القطر الأفقي
.attr("ry", 30) // نصف القطر الرأسي
.attr("fill", "purple");
المسارات هي الأدوات الأكثر مرونة وقوة لإنشاء أشكال معقدة في SVG. توفر D3 مولدات لإنشاء المسارات تلقائيًا:
// إنشاء مولد خط
const lineGenerator = d3.line()
.x(d => d.x)
.y(d => d.y)
.curve(d3.curveLinear); // نوع المنحنى (خطي)
// بيانات النقاط
const points = [
{x: 50, y: 100},
{x: 100, y: 50},
{x: 150, y: 150},
{x: 200, y: 80},
{x: 250, y: 120}
];
// إنشاء مسار باستخدام المولد
svg.append("path")
.datum(points) // ربط البيانات
.attr("d", lineGenerator) // استخدام مولد الخط
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 2);
// إنشاء مسار منحنى
const curveGenerator = d3.line()
.x(d => d.x)
.y(d => d.y)
.curve(d3.curveCatmullRom); // منحنى أكثر نعومة
svg.append("path")
.datum(points)
.attr("d", curveGenerator)
.attr("fill", "none")
.attr("stroke", "orange")
.attr("stroke-width", 2);
d3.line()
- ينشئ مولد خط.d3.area()
- ينشئ مولد منطقة.d3.arc()
- ينشئ مولد قوس (مفيد للمخططات الدائرية).d3.pie()
- يحول البيانات إلى زوايا للمخططات الدائرية.d3.stack()
- يحول البيانات للمخططات المكدسة.// بيانات للمخطط
const data = [
{month: "يناير", value: 30},
{month: "فبراير", value: 45},
{month: "مارس", value: 35},
{month: "أبريل", value: 60},
{month: "مايو", value: 40},
{month: "يونيو", value: 80}
];
// إعداد أبعاد المخطط
const margin = {top: 20, right: 30, bottom: 30, left: 40};
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
// إنشاء عنصر SVG
const svg = d3.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// إنشاء مقاييس
const x = d3.scaleBand()
.domain(data.map(d => d.month))
.range([0, width])
.padding(0.1);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.nice()
.range([height, 0]);
// إضافة المحاور
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
// إنشاء مولد خط
const line = d3.line()
.x(d => x(d.month) + x.bandwidth() / 2)
.y(d => y(d.value))
.curve(d3.curveMonotoneX);
// إضافة الخط
svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 2)
.attr("d", line);
// إضافة نقاط البيانات
svg.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", d => x(d.month) + x.bandwidth() / 2)
.attr("cy", d => y(d.value))
.attr("r", 5)
.attr("fill", "steelblue");
الانتقالات في D3.js تتيح لك إنشاء حركات سلسة وتأثيرات بصرية جذابة عند تغيير خصائص العناصر. هذا يساعد في إظهار التغييرات في البيانات بشكل أكثر وضوحًا.
// انتقال بسيط
d3.select("circle")
.transition() // بدء انتقال
.duration(1000) // المدة بالمللي ثانية
.attr("r", 50) // تغيير نصف القطر
.attr("fill", "red"); // تغيير اللون
// تأخير الانتقال
d3.select("rect")
.transition()
.delay(500) // تأخير 500 مللي ثانية
.duration(2000)
.attr("width", 300);
// دالة التوقيت (Easing)
d3.selectAll(".bar")
.transition()
.duration(1000)
.ease(d3.easeElastic) // دالة توقيت مرنة
.attr("height", d => d * 2);
// تسلسل الانتقالات
d3.select("circle")
.transition() // الانتقال الأول
.duration(1000)
.attr("r", 60)
.transition() // الانتقال الثاني
.duration(1000)
.attr("cx", 300)
.transition() // الانتقال الثالث
.duration(1000)
.attr("r", 30);
.transition()
- يبدأ انتقال على العناصر المحددة..duration(ms)
- يحدد مدة الانتقال بالمللي ثانية..delay(ms)
- يحدد تأخير قبل بدء الانتقال..ease(function)
- يحدد دالة التوقيت للانتقال.// مثال: تحديث مخطط أعمدة مع انتقالات
function updateChart(newData) {
// تحديث مقياس Y للبيانات الجديدة
const y = d3.scaleLinear()
.domain([0, d3.max(newData)])
.range([height, 0]);
// تحديث المحور Y
svg.select(".y-axis")
.transition()
.duration(1000)
.call(d3.axisLeft(y));
// ربط البيانات الجديدة
const bars = svg.selectAll(".bar")
.data(newData);
// تحديث الأعمدة الموجودة
bars.transition()
.duration(1000)
.attr("y", d => y(d))
.attr("height", d => height - y(d));
// إضافة أعمدة جديدة
bars.enter()
.append("rect")
.attr("class", "bar")
.attr("x", (d, i) => x(i))
.attr("width", x.bandwidth())
.attr("fill", "steelblue")
.attr("y", height) // بدء من الأسفل للحصول على تأثير التحريك لأعلى
.attr("height", 0)
.transition() // تطبيق انتقال
.duration(1000)
.attr("y", d => y(d))
.attr("height", d => height - y(d));
// إزالة الأعمدة الزائدة
bars.exit()
.transition()
.duration(1000)
.attr("y", height)
.attr("height", 0)
.remove();
}
.style()
.دوال التوقيت تتحكم في سرعة الانتقال خلال مدته، مما يتيح تأثيرات مختلفة:
// أنواع مختلفة من دوال التوقيت
const easingFunctions = [
d3.easeLinear, // خطي (سرعة ثابتة)
d3.easeQuad, // تربيعي
d3.easeCubic, // تكعيبي
d3.easeSin, // جيبي
d3.easeExp, // أسي
d3.easeElastic, // مرن
d3.easeBounce // ارتدادي
];
// مثال على استخدام دالة توقيت
d3.select("circle")
.transition()
.duration(2000)
.ease(d3.easeBounce) // تأثير الارتداد
.attr("cy", 300);
يتيح D3.js ربط معالجات الأحداث بالعناصر، مما يجعل الرسومات البيانية تفاعلية. يمكنك الاستجابة لأحداث المستخدم مثل النقر، التحويم، السحب، وغيرها.
// ربط حدث النقر
d3.select("circle")
.on("click", function(event, d) {
console.log("تم النقر على الدائرة!");
console.log("البيانات:", d);
console.log("الحدث:", event);
// يمكن الوصول إلى العنصر الذي تم النقر عليه
d3.select(this).attr("fill", "red");
});
// ربط أحداث الفأرة
d3.selectAll(".bar")
.on("mouseover", function(event, d) {
// عند تحويم الفأرة فوق العنصر
d3.select(this)
.transition()
.duration(200)
.attr("fill", "orange");
})
.on("mouseout", function(event, d) {
// عند إزالة الفأرة من العنصر
d3.select(this)
.transition()
.duration(500)
.attr("fill", "steelblue");
});
// إزالة معالج حدث
d3.select("rect").on("click", null); // إزالة معالج النقر
.on(eventName, listener)
- يربط معالج حدث بالعناصر المحددة.this
إلى عنصر DOM الذي حدث فيه الحدث.event
تحتوي على معلومات الحدث.d
تحتوي على البيانات المرتبطة بالعنصر.// إضافة حاوية للتلميح
const tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("background-color", "white")
.style("border", "1px solid #ddd")
.style("border-radius", "4px")
.style("padding", "10px")
.style("box-shadow", "0 2px 4px rgba(0,0,0,0.1)")
.style("visibility", "hidden"); // مخفي بشكل افتراضي
// ربط أحداث بعناصر الرسم البياني
d3.selectAll(".data-point")
.on("mouseover", function(event, d) {
// اظهار التلميح
tooltip
.style("visibility", "visible")
.html(`
${d.name}
القيمة: ${d.value}
نسبة التغيير: ${d.change}%
`);
})
.on("mousemove", function(event) {
// تحريك التلميح مع الفأرة
tooltip
.style("left", (event.pageX + 15) + "px")
.style("top", (event.pageY - 30) + "px");
})
.on("mouseout", function() {
// إخفاء التلميح
tooltip.style("visibility", "hidden");
});
// تمكين السحب باستخدام d3.drag
const drag = d3.drag()
.on("start", function(event, d) {
// عند بدء السحب
d3.select(this).raise().attr("stroke", "black");
})
.on("drag", function(event, d) {
// أثناء السحب
d3.select(this)
.attr("cx", d.x = event.x)
.attr("cy", d.y = event.y);
})
.on("end", function(event, d) {
// عند انتهاء السحب
d3.select(this).attr("stroke", null);
});
// تطبيق السلوك على العناصر
d3.selectAll("circle").call(drag);
// مثال آخر: التكبير والتصغير (Zoom)
const zoom = d3.zoom()
.scaleExtent([0.5, 5]) // حدود التكبير (0.5x إلى 5x)
.on("zoom", function(event) {
// تحديث المجموعة عند التكبير/التصغير
g.attr("transform", event.transform);
});
// تطبيق سلوك التكبير على SVG
svg.call(zoom);
D3 يوفر عدة سلوكيات (behaviors) جاهزة للتفاعل:
d3.drag()
- للتعامل مع سحب العناصر.d3.zoom()
- للتعامل مع التكبير والتصغير والتحريك.d3.brush()
- لتحديد مناطق بالسحب.التخطيطات في D3 هي خوارزميات تحول البيانات المجردة إلى هياكل مرئية. تحسب هذه التخطيطات القيم الهندسية اللازمة لإنشاء تصورات معقدة.
// البيانات
const data = [
{category: "فئة أ", value: 30},
{category: "فئة ب", value: 15},
{category: "فئة ج", value: 25},
{category: "فئة د", value: 10},
{category: "فئة هـ", value: 20}
];
// إنشاء SVG
const width = 450;
const height = 450;
const svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height);
// حساب نصف القطر
const radius = Math.min(width, height) / 2;
// إنشاء مجموعة وتحريكها إلى المنتصف
const g = svg.append("g")
.attr("transform", `translate(${width/2}, ${height/2})`);
// إنشاء مقياس للألوان
const color = d3.scaleOrdinal()
.domain(data.map(d => d.category))
.range(d3.schemeCategory10);
// إنشاء تخطيط الدائرة
const pie = d3.pie()
.value(d => d.value)
.sort(null); // لا ترتيب
// إنشاء مولد قوس
const arc = d3.arc()
.innerRadius(0) // للمخطط الدائري التقليدي
.outerRadius(radius - 10);
// إنشاء مولد قوس للتسميات
const labelArc = d3.arc()
.innerRadius(radius - 80)
.outerRadius(radius - 80);
// إنشاء العناصر المرئية
const arcs = g.selectAll(".arc")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc");
// إضافة المسارات
arcs.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.category))
.attr("stroke", "white")
.style("stroke-width", "2px");
// إضافة التسميات
arcs.append("text")
.attr("transform", d => `translate(${labelArc.centroid(d)})`)
.attr("text-anchor", "middle")
.text(d => d.data.category);
// إضافة تلميحات عند التحويم
arcs.on("mouseover", function(event, d) {
d3.select(this).select("path")
.transition()
.duration(200)
.attr("d", d3.arc()
.innerRadius(0)
.outerRadius(radius - 5));
})
.on("mouseout", function() {
d3.select(this).select("path")
.transition()
.duration(200)
.attr("d", arc);
});
// بيانات التسلسل الهرمي
const data = {
name: "الجذر",
children: [
{
name: "المجموعة أ",
children: [
{ name: "العنصر أ1", value: 100 },
{ name: "العنصر أ2", value: 150 }
]
},
{
name: "المجموعة ب",
children: [
{ name: "العنصر ب1", value: 200 },
{ name: "العنصر ب2", value: 120 },
{ name: "العنصر ب3", value: 80 }
]
},
{ name: "العنصر ج", value: 300 }
]
};
// إنشاء تسلسل هرمي من البيانات
const root = d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value);
// إنشاء تخطيط خريطة الشجرة
const treemap = d3.treemap()
.size([width, height])
.padding(2);
// تطبيق التخطيط على البيانات
treemap(root);
// إنشاء مقياس للألوان
const color = d3.scaleOrdinal()
.domain(["المجموعة أ", "المجموعة ب", "العنصر ج"])
.range(["#66c2a5", "#fc8d62", "#8da0cb"]);
// إضافة العقد إلى SVG
const cell = svg.selectAll("g")
.data(root.leaves())
.join("g")
.attr("transform", d => `translate(${d.x0},${d.y0})`);
// إضافة المستطيلات
cell.append("rect")
.attr("width", d => d.x1 - d.x0)
.attr("height", d => d.y1 - d.y0)
.attr("fill", d => {
while (d.depth > 1) d = d.parent;
return color(d.data.name);
})
.attr("stroke", "#fff");
// إضافة النص
cell.append("text")
.attr("x", 3)
.attr("y", 15)
.text(d => d.data.name)
.attr("font-size", "10px")
.attr("fill", "white");
توفر D3 العديد من التخطيطات الأخرى:
بعد إتقان المفاهيم الأساسية، يمكنك الانتقال إلى موضوعات أكثر تقدمًا في D3.js.
// إنشاء مكون مخطط الأعمدة قابل لإعادة الاستخدام
function barChart() {
// الإعدادات الافتراضية
let width = 600;
let height = 400;
let margin = { top: 20, right: 20, bottom: 30, left: 40 };
let xValue = d => d[0];
let yValue = d => d[1];
let xScale = d3.scaleBand();
let yScale = d3.scaleLinear();
let color = "steelblue";
// الدالة الرئيسية للمخطط
function chart(selection) {
selection.each(function(data) {
// تحديث نطاقات المقاييس
xScale
.domain(data.map(xValue))
.range([0, width - margin.left - margin.right])
.padding(0.1);
yScale
.domain([0, d3.max(data, yValue)])
.nice()
.range([height - margin.top - margin.bottom, 0]);
// تحديد SVG
const svg = d3.select(this)
.selectAll("svg")
.data([data]);
// إنشاء العناصر الأولية
const svgEnter = svg.enter().append("svg");
const gEnter = svgEnter.append("g");
// إعداد المحاور
gEnter.append("g").attr("class", "x-axis");
gEnter.append("g").attr("class", "y-axis");
// تحديث حجم SVG
svg.merge(svgEnter)
.attr("width", width)
.attr("height", height);
// تحديث موضع المجموعة
const g = svg.merge(svgEnter).select("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// تحديث محور X
g.select(".x-axis")
.attr("transform", `translate(0,${height - margin.top - margin.bottom})`)
.call(d3.axisBottom(xScale));
// تحديث محور Y
g.select(".y-axis")
.call(d3.axisLeft(yScale));
// ربط البيانات بالأعمدة
const bars = g.selectAll(".bar")
.data(data);
// إدخال الأعمدة الجديدة
bars.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => xScale(xValue(d)))
.attr("y", height - margin.top - margin.bottom)
.attr("width", xScale.bandwidth())
.attr("height", 0)
.attr("fill", color)
.merge(bars)
.transition()
.duration(1000)
.attr("x", d => xScale(xValue(d)))
.attr("y", d => yScale(yValue(d)))
.attr("width", xScale.bandwidth())
.attr("height", d => height - margin.top - margin.bottom - yScale(yValue(d)));
// إزالة الأعمدة الزائدة
bars.exit()
.transition()
.duration(1000)
.attr("y", height - margin.top - margin.bottom)
.attr("height", 0)
.remove();
});
}
// الدوال المساعدة لضبط الخصائص
chart.width = function(value) {
if (!arguments.length) return width;
width = value;
return chart;
};
chart.height = function(value) {
if (!arguments.length) return height;
height = value;
return chart;
};
chart.margin = function(value) {
if (!arguments.length) return margin;
margin = value;
return chart;
};
chart.x = function(value) {
if (!arguments.length) return xValue;
xValue = value;
return chart;
};
chart.y = function(value) {
if (!arguments.length) return yValue;
yValue = value;
return chart;
};
chart.color = function(value) {
if (!arguments.length) return color;
color = value;
return chart;
};
return chart;
}
// استخدام المكون
const myChart = barChart()
.width(800)
.height(500)
.margin({ top: 30, right: 30, bottom: 40, left: 50 })
.color("#3498db");
// تطبيق المخطط على البيانات
d3.select("#chart")
.datum(data)
.call(myChart);
يمكنك إنشاء عدة رسوم بيانية تتفاعل مع بعضها البعض:
// مثال بسيط للرسوم البيانية المتناسقة
function updateCharts(selectedData) {
// تحديث المخطط الأول
chart1.update(selectedData);
// تحديث المخطط الثاني
chart2.update(selectedData);
// تحديث المخطط الثالث
chart3.update(selectedData);
}
// ربط حدث التحديد بالمخطط الأول
d3.select("#chart1")
.selectAll(".bar")
.on("click", function(event, d) {
// تصفية البيانات بناءً على العنصر المحدد
const filtered = fullData.filter(item =>
item.category === d.category
);
// تحديث جميع المخططات
updateCharts(filtered);
});
يمكن دمج D3.js مع أطر عمل مثل React أو Vue أو Angular:
// مثال بسيط لدمج D3 مع React
import React, { useRef, useEffect } from 'react';
import * as d3 from 'd3';
function BarChart({ data }) {
const chartRef = useRef();
useEffect(() => {
// إنشاء المخطط عند تحميل المكون
const svg = d3.select(chartRef.current)
.append('svg')
.attr('width', 600)
.attr('height', 400);
// إنشاء المقاييس والمحاور
// ...
// رسم الأعمدة
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', (d, i) => i * 70)
.attr('y', d => 400 - d.value * 10)
.attr('width', 65)
.attr('height', d => d.value * 10)
.attr('fill', 'steelblue');
// التنظيف عند إزالة المكون
return () => {
d3.select(chartRef.current).selectAll('*').remove();
};
}, [data]); // إعادة الرسم عند تغيير البيانات
return ;
}