كيفية إنشاء مخططات أسعار تاريخية باستخدام D3.js

نهج تدريجي نحو تصور مجموعات البيانات المالية

الصورة بواسطة كيفن كو من Pexels

إنه تحدٍ عند توصيل البيانات وعرض هذه المرئيات على أجهزة ومنصات متعددة.

"البيانات هي تماما مثل الخام. إنها قيمة ، لكن إذا لم يتم تحسينها ، فلن يتم استخدامها حقًا. "- مايكل بالمر

D3 (المستندات التي تعتمد على البيانات) تحل هذه المعضلة القديمة. إنه يوفر للمطورين والمحللين القدرة على إنشاء تصورات مخصصة للويب بحرية كاملة. يسمح لنا D3.js بربط البيانات بـ DOM (طراز كائن المستند). ثم قم بتطبيق التحويلات التي تعتمد على البيانات لإنشاء تصورات محسنة للبيانات.

في هذا البرنامج التعليمي ، سوف نفهم كيف يمكننا جعل مكتبة D3.js تعمل من أجلنا.

ابدء

سنبني مخططًا يوضح حركة الأداة المالية على مدار فترة زمنية. يشبه هذا التصور المخططات السعرية المقدمة من Yahoo Finance. سنقوم بتفكيك المكونات المختلفة المطلوبة لتقديم مخطط أسعار تفاعلي يتتبع سهم معين.

المكونات المطلوبة:

  1. تحميل وتحليل البيانات
  2. عنصر SVG
  3. محاور X و Y
  4. إغلاق خط الرسم البياني للسعر
  5. منحنى متوسط ​​الرسم البياني المتحرك البسيط مع بعض الحسابات
  6. الرسم البياني لسلسلة وحدات التخزين
  7. الفأر التقاطع والأسطورة

تحميل وتحليل البيانات

const loadData = d3.json ('sample-data.json'). ثم (data => {
  const chartResultsData = data ['chart'] ['result'] [0]؛
  const quoteData = chartResultsData ['المؤشرات'] ['quote'] [0] ؛
  إرجاع chartResultsData ['الطابع الزمني']. map ((time، index) => ({
    التاريخ: التاريخ الجديد (الوقت * 1000) ،
    high: quoteData ['high'] [الفهرس] ،
    low: quoteData ['low'] [الفهرس] ،
    open: quoteData ['open'] [الفهرس] ،
    close: quoteData ['close'] [الفهرس] ،
    وحدة التخزين: quoteData ['volume'] [الفهرس]
  }))؛
})؛

أولاً ، سوف نستخدم وحدة الجلب لتحميل بيانات العينات الخاصة بنا. يدعم D3-fetch أيضًا تنسيقات أخرى مثل ملفات TSV و CSV. سيتم بعد ذلك معالجة البيانات لإرجاع مجموعة من الكائنات. يحتوي كل كائن على الطابع الزمني للتداول والسعر المرتفع والسعر المنخفض والسعر المفتوح وسعر الإغلاق وحجم التداول.

الجسم {
  الخلفية: # 00151c؛
}
#chart {
  الخلفية: # 0e3040 ؛
  اللون: # 67809f ؛
}

أضف خصائص CSS الأساسية أعلاه لإضفاء طابع شخصي على نمط المخطط للحصول على أقصى جاذبية بصرية.

إلحاق عنصر SVG

const initialiseChart = data => {
  const margin = {top: 50، right: 50، bottom: 50، left: 50}؛
  عرض const = window.innerWidth - margin.left - margin.right؛
  const const = window.innerHeight - margin.top - margin.bottom؛
  / / أضف SVG إلى الصفحة
  const svg = d3
    . تحديد ( '# الرسم البياني لل)
    .append ( 'SVG')
    .attr ('العرض' ، العرض + الهامش ['left'] + الهامش ['right'])
    .attr ('ارتفاع' ، ارتفاع + هامش ['أعلى'] + هامش ['أسفل'])
    .call (responsivefy)
    .append ( 'ز')
    .attr ('convert'، `translate ($ {margin ['left']}، $ {margin ['top']})`)؛

بعد ذلك ، يمكننا استخدام طريقة append () لإلحاق عنصر SVG بعنصر

بالمُعرّف ، المخطط. بعد ذلك ، نستخدم طريقة attr () لتعيين عرض وارتفاع عنصر SVG. ثم نسمي طريقة responsivefy () المكتوبة في الأصل بواسطة Brendan Sudol). يتيح ذلك لعنصر SVG أن يتمتع بقدرات استجابة من خلال الاستماع إلى أحداث تغيير حجم النافذة.

تذكر إلحاق عنصر مجموعة SVG بعنصر SVG أعلاه قبل ترجمته باستخدام القيم من ثابت الهامش.

تقديم محاور X و Y

قبل تقديم مكون المحاور ، سوف نحتاج إلى تحديد مجالنا ونطاقنا ، والذي سيتم استخدامه بعد ذلك لإنشاء مقاييس لدينا للمحاور

// العثور على نطاق البيانات
const xMin = d3.min (data، d => {
  عودة د ['التاريخ'] ؛
})؛
const xMax = d3.max (data، d => {
  عودة د ['التاريخ'] ؛
})؛
const yMin = d3.min (data، d => {
  عودة د ['وثيقة'] ؛
})؛
const yMax = d3.max (data، d => {
  عودة د ['وثيقة'] ؛
})؛
/ / جداول المخططات
const xScale = d3
  .scaleTime ()
  .domain ([xMin، xMax])
  .range ([0 ، عرض]) ؛
const yScale = d3
  .scaleLinear ()
  .domain ([yMin - 5، yMax])
  .range ([ارتفاع ، 0]) ؛

يتكون المحوران x و y في مخطط خط سعر الإغلاق من تاريخ التداول وسعر الإغلاق على التوالي. لذلك ، يتعين علينا تحديد الحد الأدنى والحد الأقصى لقيم x و y ، باستخدام d3.max () و d3.min (). يمكننا بعد ذلك الاستفادة من مقياس D3-scale'sTimeTime () و scaleLinear () لإنشاء المقياس الزمني على المحور السيني والمقياس الخطي على المحور ص على التوالي. يتم تعريف نطاق المقاييس بعرض وعرض عنصر SVG الخاص بنا.

/ / قم بإنشاء مكون المحاور
SVG
  .append ( 'ز')
  .attr ('id' ، 'xAxis')
  .attr ('convert'، `translate (0، $ {height})`)
  .call (d3.axisBottom (معالج xscale))؛
SVG
  .append ( 'ز')
  .attr ('id' ، 'yAxis')
  .attr ('convert'، `translate ($ {width}، 0)`)
  .call (d3.axisRight (yScale))؛

بعد هذه الخطوة ، نحتاج إلى إلحاق العنصر g الأول بعنصر SVG ، والذي يستدعي طريقة d3.axisBottom () ، مع الأخذ في xScale كمعلمة لإنشاء المحور السيني. ثم يتم ترجمة المحور السيني إلى أسفل منطقة المخطط. وبالمثل ، يتم إنشاء المحور ص عن طريق إلحاق عنصر g ، باستدعاء d3.axisRight () مع yScale كمعلمة ، قبل ترجمة المحور ص إلى يمين منطقة المخطط.

تقديم مخطط خط سعر الإغلاق

/ / ينشئ مخطط خط سعر قريب عند الاتصال به
خط const = d3
  .خط()
  .x (d => {
    إرجاع xScale (d ['date']) ؛
  })
  .y (d => {
    إرجاع yScale (d ['close']) ؛
  })؛
// إلحاق المسار وربط البيانات
SVG
 .append ( 'المسار')
 .data ([البيانات])
 .نمط ('ملء' ، 'لا شيء')
 .attr ('id' ، 'priceChart')
 .attr ('stroke' ، 'steelblue')
 .attr ('عرض السكتة الدماغية' ، '1.5')
 .attr ('d' ، سطر) ؛

الآن ، يمكننا إلحاق عنصر المسار داخل عنصر SVG الرئيسي الخاص بنا ، متبوعًا بتمرير مجموعة بياناتنا التي تم تحليلها. وضعنا السمة d من خلال وظيفة المساعد لدينا. الذي يستدعي طريقة d3.line (). تقبل سمات x و y للخط الدالات المجهولة وإرجاع التاريخ وسعر الإغلاق على التوالي.

الآن ، هذه هي الطريقة التي يجب أن يظهر بها المخطط:

نقطة التفتيش رقم 1: قم بإغلاق مخطط خط الأسعار ، مع محاور X و Y.

تقديم المنحنى المتوسط ​​المتحرك البسيط

بدلاً من الاعتماد فقط على سعر الإغلاق باعتباره الشكل الوحيد لمؤشرنا الفني ، فإننا نستخدم المتوسط ​​المتحرك البسيط. يحدد هذا المتوسط ​​اتجاهات صعودية واتجاهية للأمان المعين.

const movingAmediate = (data، numberOfPricePoints) => {
  إرجاع data.map ((الصف ، الفهرس ، الإجمالي) => {
    const start = Math.max (0 ، index - numberOfPricePoints)؛
    مؤشر النهاية
    const subset = total.slice (start، end + 1)؛
    const sum = subset.reduce ((a، b) => {
      إرجاع a + b ['close'] ؛
    } ، 0) ؛
    إرجاع {
      التاريخ: الصف ['التاريخ'] ،
      المتوسط: مجموع / مجموعة فرعية
    }؛
  })؛
}؛

نحدد وظيفة المساعد لدينا ، movingAmediate لحساب المتوسط ​​المتحرك البسيط. تقبل هذه الوظيفة معلمتين ، هما مجموعة البيانات وعدد نقاط السعر أو الفترات. ثم تقوم بإرجاع مجموعة من الكائنات ، مع كل كائن يحتوي على تاريخ ومعدل كل نقطة بيانات.

/ / يحسب المتوسط ​​المتحرك البسيط على مدار 50 يومًا
const movingAmediateData = movingAmediate (data، 49)؛
/ / يولد منحنى متوسط ​​متحرك عند الاتصال به
const movingAmediateLine = d3
 .خط()
 .x (d => {
  إرجاع xScale (d ['date']) ؛
 })
 .y (d => {
  إرجاع yScale (d ['average']) ؛
 })
  .curve (d3.curveBasis)؛
SVG
  .append ( 'المسار')
  .data ([movingAverageData])
  .نمط ('ملء' ، 'لا شيء')
  .attr ('id' ، 'movingA AverageLine')
  .attr ('stroke' ، '# FF8900')
  .attr ('d' ، movingA AverageLine) ؛

بالنسبة إلى السياق الحالي لدينا ، فإن movingAmediate () يحسب المتوسط ​​المتحرك البسيط على مدار 50 يومًا. على غرار مخطط خط الأسعار القريب ، نقوم بإلحاق عنصر المسار داخل عنصر SVG الرئيسي الخاص بنا ، متبوعًا بتمرير مجموعة بيانات المتوسط ​​المتحرك ، وتحديد السمة d من خلال وظيفة المساعد الخاصة بنا ، movingAmediateLine. الفرق الوحيد مما سبق هو أننا انتقلنا d3.curveBasis إلى d3.line (). curve () من أجل تحقيق المنحنى.

ينتج عن هذا منحنى متوسط ​​الحركة البسيط المغطى أعلى مخططنا الحالي:

نقطة التفتيش رقم 2: منحنى برتقالي ، يصور المتوسط ​​المتحرك البسيط. هذا يعطينا فكرة أفضل عن حركة السعر.

تقديم مخطط شريط سلسلة المجلد

بالنسبة لهذا المكون ، سنجعل حجم التداول في شكل مخطط شريطي مرمز بالألوان يشغل نفس عنصر SVG. تكون الحانات خضراء عندما يغلق السهم أعلى من سعر إغلاق اليوم السابق. تكون حمراء عندما يغلق السهم أدنى من سعر إغلاق اليوم السابق. هذا يوضح حجم التداول لكل تاريخ تداول. يمكن بعد ذلك استخدام هذا إلى جانب المخطط أعلاه لتحليل تحركات الأسعار.

/ * أشرطة سلسلة وحدة التخزين * /
const volData = data.filter (d => d ['volume']! == null && d ['volume']! == 0)؛
const yMinVolume = d3.min (volData، d => {
  إرجاع Math.min (d ['volume']) ؛
})؛
const yMaxVolume = d3.max (volData، d => {
  إرجاع Math.max (d ['volume']) ؛
})؛
const yVolumeScale = d3
  .scaleLinear ()
  .domain ([yMinVolume، yMaxVolume])
  .range ([ارتفاع ، 0]) ؛

يتكون المحوران x و y للمخطط الشريطي لسلسلة الحجم من تاريخ التداول وحجمه على التوالي. وبالتالي ، سنحتاج إلى إعادة تحديد الحد الأدنى والحد الأقصى لقيم y والاستفادة من scaleLinear () على المحور ص. يتم تعريف نطاق هذه المقاييس بعرض وارتفاع عنصر SVG الخاص بنا. سنقوم بإعادة استخدام xScale لأن المحور السيني للمخطط الشريطي يتوافق مع تاريخ التداول.

SVG
  .اختر الكل()
  .data (volData)
  .أدخل()
  .append ( 'المستطيل')
  .attr ('x'، d => {
    إرجاع xScale (d ['date']) ؛
  })
  .attr ('y'، d => {
    إرجاع yVolumeScale (d ['volume']) ؛
  })
  .attr ('fill' ، (d ، i) => {
    إذا (أنا === 0) {
      إرجاع "# 03a678" ؛
    } آخر {
      إرجاع volData [i - 1]. إغلاق> d.close؟ '# c0392b': '# 03a678' ؛
    }
  })
  .attr ('العرض' ، 1)
  .attr ('ارتفاع' ، د => {
    ارتفاع الإرجاع - yVolumeScale (d ['volume']) ؛
  })؛

يعتمد هذا القسم على تفهمك لكيفية عمل طريقة theselectAll () مع أساليب الإدخال () وإلحاق (). قد ترغب في قراءة هذا (كتبه مايك بوستوك نفسه) إذا كنت غير معتاد على هذه الأساليب. قد يكون هذا مهمًا حيث يتم استخدام هذه الأساليب كجزء من نمط إدخال التحديث - الخروج ، والذي قد أغطيه في برنامج تعليمي لاحق.

لعرض الأشرطة ، سنستخدم أولاً .selectAll () لإرجاع تحديد فارغ أو صفيف فارغ. بعد ذلك ، نقوم بتمرير volData لتحديد ارتفاع كل شريط. تقارن طريقة الإدخال () مجموعة بيانات volData مع التحديد من selectAll () ، وهو فارغ حاليًا. حاليًا ، لا يحتوي DOM على أي عنصر . وبالتالي ، فإن طريقة append () تقبل الوسيطة 'rect' ، والتي تنشئ عنصرًا جديدًا في DOM لكل كائن فردي في volData.

هنا هو انهيار سمات القضبان. سنستخدم السمات التالية: س ، ص ، التعبئة ، العرض ، والارتفاع.

.attr ('x'، d => {
  إرجاع xScale (d ['date']) ؛
})
.attr ('y'، d => {
  إرجاع yVolumeScale (d ['volume']) ؛
})

تعرّف طريقة attr () الأولى إحداثي س. يقبل وظيفة مجهولة تقوم بإرجاع التاريخ. وبالمثل ، تحدد الطريقة attr () الثانية إحداثي y. يقبل وظيفة مجهولة تقوم بإرجاع وحدة التخزين. هذه سوف تحدد موقف كل شريط.

.attr ('العرض' ، 1)
.attr ('ارتفاع' ، د => {
  ارتفاع الإرجاع - yVolumeScale (d ['volume']) ؛
})؛

نحن نخصص عرض 1 بكسل لكل شريط. لجعل الشريط يمتد من الأعلى (المعرّف بواسطة y) إلى المحور السيني ، قم ببساطة بخصم الارتفاع بقيمة y.

.attr ('fill' ، (d ، i) => {
  إذا (أنا === 0) {
    إرجاع "# 03a678" ؛
  } آخر {
    إرجاع volData [i - 1]. إغلاق> d.close؟ '# c0392b': '# 03a678' ؛
  }
})

تذكر الطريقة التي سيتم بها ترميز الأعمدة؟ سنستخدم سمة الملء لتحديد ألوان كل شريط. بالنسبة إلى الأسهم التي أغلقت أعلى من سعر إغلاق اليوم السابق ، سيكون الشريط أخضر اللون. خلاف ذلك ، سيكون الشريط أحمر.

هذه هي الطريقة التي يجب أن يظهر بها المخطط الحالي الخاص بك:

نقطة التفتيش رقم 3: مخطط سلسلة الحجم ، ويمثله أشرطة حمراء وخضراء.

تقديم التقاطع والأسطورة للتفاعل

لقد وصلنا إلى الخطوة الأخيرة من هذا البرنامج التعليمي ، حيث سننشئ تقاطع تمرير الماوس الذي يعرض خطوط الإسقاط. سيؤدي الفأر على النقاط المختلفة في المخطط إلى تحديث الأساطير. هذا يوفر لنا المعلومات الكاملة (السعر المفتوح ، سعر الإغلاق ، السعر المرتفع ، السعر المنخفض ، الحجم) لكل تاريخ تداول.

تمت الإشارة إلى القسم التالي من مثال Micah Stubb الممتاز.

// يجعل x و y التقاطع
التركيز const = svg
  .append ( 'ز')
  .attr ('class' ، 'focus')
  .style ('display'، 'none')؛
focus.append ('circle'). attr ('r'، 4.5)؛
focus.append ('line'). classed ('x'، true)؛
focus.append ('line'). مصنفة ('y' ، صواب) ؛
SVG
  .append ( 'المستطيل')
  .attr ('class' ، 'overlay')
  .attr ('العرض' ، العرض)
  .attr ("الارتفاع" ، الارتفاع)
  .on ('mouseover'، () => focus.style ('display'، null))
  .on ('mouseout'، () => focus.style ('display'، 'none'))
  .في ('mousemove' ، genericross) ؛
d3.select ('. overlay'). style ('fill'، 'none')؛
d3.select ('. overlay'). style ('pointer-events'، 'all')؛
d3.selectAll ('. focus line'). style ('fill'، 'none')؛
d3.selectAll ('. focus line'). style ('stroke'، '# 67809f')؛
d3.selectAll ('. خط التركيز'). النمط ('عرض السكتة الدماغية' ، '1.5px') ؛
d3.selectAll ('. focus line'). style ('stroke-dasharray'، '3 3')؛

يتكون التقاطع من دائرة شفافة مع خطوط إسقاط تتكون من شرطات. يوفر رمز الكود أعلاه تصميم العناصر الفردية. عند تمرير الماوس ، سيتم إنشاء التقاطع استنادًا إلى الوظيفة أدناه.

const bisectDate = d3.bisector (d => d.date) .left؛
وظيفة توليد كروسهير () {
  / ترجع القيمة المقابلة من المجال
  const الموافق التاريخ = xScale.invert (d3.mouse (هذا) [0]) ؛
  // يحصل على نقطة الإدراج
  const i = bisectDate (البيانات ، الموافق ، 1) ؛
  const d0 = البيانات [i - 1] ؛
  const d1 = البيانات [i] ؛
  const currentPoint = الموافق التاريخ - d0 ['date']> d1 ['date'] - الموافق المقابل؟ d1: d0 ؛
  
  focus.attr ('convert'، `translate ($ {xScale (currentPoint ['date'])}، $ {yScale (currentPoint ['close'])})`)؛
التركيز
  . تحديد ( 'line.x')
  .attr ('x1' ، 0)
  .attr ('x2' ، العرض - xScale (currentPoint ['date']))
  .attr ('y1' ، 0)
  .attr ('y2' ، 0) ؛
التركيز
  . تحديد ( 'line.y')
  .attr ('x1' ، 0)
  .attr ('x2' ، 0)
  .attr ('y1' ، 0)
  .attr ('y2' ، ارتفاع - yScale (currentPoint ['close'])) ؛
 updateLegends (currentPoint)؛
}

يمكننا بعد ذلك استخدام طريقة d3.bisector () لتحديد نقطة الإدراج ، والتي ستسلط الضوء على أقرب نقطة بيانات على الرسم البياني لخط سعر الإغلاق. بعد تحديد CurrentPoint ، سيتم تحديث خطوط الإسقاط. يستخدم أسلوب updateLegends () التيار الحالي كمعلمة.

const updateLegends = currentData => {
  . d3.selectAll ( '. lineLegend') إزالة ()؛
  const legendKeys = Object.keys (data [0]) ؛
  const lineLegend = svg
    .selectAll ( '. lineLegend')
    .data (legendKeys)
    .أدخل()
    .append ( 'ز')
    .attr ('class' ، 'lineLegend')
    .attr ('convert'، (d، i) => {
      return `translate (0، $ {i * 20})`؛
    })؛
  lineLegend
    .append ( 'النص')
    .text (d => {
      if (d === 'date') {
        إرجاع `$ {d}: $ {currentData [d] .toLocaleDateString ()}`؛
      } آخر إذا (d === 'high' || d === 'low' || d === 'open' || d === 'close') {
        إرجاع `$ {d}: $ {currentData [d] .toFixed (2)}`؛
      } آخر {
        إرجاع `$ {d}: $ {currentData [d]}`؛
      }
    })
    .نمط ("ملء" ، "أبيض")
    .attr ('convert'، 'translate (15،9)')؛
  }؛

تقوم أداة updateLegends () بتحديث وسيلة الإيضاح عن طريق عرض التاريخ والسعر المفتوح وسعر الإغلاق والسعر المرتفع والسعر المنخفض وحجم نقطة الماوس المحددة على الرسم البياني للخط القريب. على غرار مخططات شريط الصوت ، سنستخدم طريقة selectAll () مع أساليب الإدخال () وإلحاق ().

لتقديم وسيلة الإيضاح ، سنستخدم. حدد All ('. lineLegend') لتحديد وسيلة الإيضاح ، يليها استدعاء طريقة الإزالة () لإزالتها. بعد ذلك ، نقوم بتمرير مفاتيح الأساطير ، legendKeys ، والتي سيتم استخدامها لتحديد ارتفاع كل شريط. يتم استدعاء الأسلوب enter () ، الذي يقارن مجموعة بيانات volData وعند التحديد من selectAll () ، وهو فارغ حاليًا. حاليًا ، لا يحتوي DOM على أي عنصر . وبالتالي ، فإن طريقة append () تقبل الوسيطة 'rect' ، والتي تنشئ عنصرًا جديدًا في DOM لكل كائن فردي في volData.

بعد ذلك ، إلحاق الأساطير مع خصائصها. نقوم بمعالجة القيم عن طريق تحويل الأسعار إلى منزلتين عشريتين. لقد قمنا أيضًا بتعيين كائن التاريخ إلى الإعدادات الافتراضية لسهولة القراءة.

ستكون هذه هي النتيجة النهائية:

نقطة تفتيش رقم 4: Mouseover أي جزء من المخطط!

ختام الأفكار

تهانينا! لقد وصلت إلى نهاية هذا البرنامج التعليمي. كما هو موضح أعلاه ، D3.js بسيط ولكنه ديناميكي. يسمح لك بإنشاء تصورات مخصصة لجميع مجموعات البيانات الخاصة بك. في الأسابيع المقبلة ، سأصدر الجزء الثاني من هذه السلسلة والذي سيغوص عميقًا في نمط الدخول إلى التحديث الخاص بـ D3.js. وفي الوقت نفسه ، قد ترغب في الاطلاع على وثائق واجهة برمجة التطبيقات والمزيد من البرامج التعليمية وتصورات أخرى مثيرة للاهتمام تم إنشاؤها باستخدام D3.js.

لا تتردد في التحقق من شفرة المصدر وكذلك العرض التوضيحي الكامل لهذا البرنامج التعليمي. شكرا لك ، وآمل أن تكون قد تعلمت شيئا جديدا اليوم!

شكر خاص لديبي ليونغ لمراجعتها هذه المقالة.