Measuring the progress of the global movement for Palestinian liberation.
I started this document on November 7, 2023, and I work on it daily. Please check back for changes and updates.
Tracking the health and progress of the global movement for Palestinian liberation. This tool seeks to answer the question, “Is it having any impact?”
Population effects in Palestine
I am attempting to measure the impact on the population – whether it be in lives, homes, abilities, or health that is lost. 1
⚠️ This visualization will soon be replaced with something that shows more context.
Code
population_loss_chart = {// Specify the chart’s dimensions.const width =928;const height =Math.min(width,500);// Create the color scale.const color = d3.scaleOrdinal().domain(population_loss_in_palestine.map(d => d.name)).range(d3.quantize(t => d3.interpolateSpectral(t *0.8+0.1), population_loss_in_palestine.length).reverse())// Create the pie layout and arc generator.const pie = d3.pie().sort(null).value(d => d.value);const arc = d3.arc().innerRadius(0).outerRadius(Math.min(width, height) /2-1);const labelRadius = arc.outerRadius()() *0.8;// A separate arc generator for labels.const arcLabel = d3.arc().innerRadius(labelRadius).outerRadius(labelRadius);const arcs =pie(population_loss_in_palestine);// Create the SVG container.const svg = d3.create("svg").attr("class","panel-fill").attr("width", width).attr("height", height).attr("viewBox", [-width /2,-height /2, width, height]).attr("style","max-width: 100%; height: auto; font: 10px sans-serif;");// Add a sector path for each value. svg.append("g").attr("stroke","white").selectAll().data(arcs).join("path").attr("fill", d => d.data.color).attr("d", arc).append("title").text(d =>`${d.data.name}: ${d.data.value.toLocaleString("en-US")}`);// Create a new arc generator to place a label close to the edge. svg.append("g").attr("text-anchor","middle").selectAll().data(arcs).join("text").attr("transform", d =>`translate(${arcLabel.centroid(d)})`).call(text => text.append("tspan").attr("y","-0.4em").attr("x", (d) => {if (d.data.name==="Survivors") {return"0.8em" }if (d.data.name==="Displaced") {return"-0.8em" }return0 }).attr("font-weight","bold").attr("font-size","24px").attr("fill","#fff").text(d => d.data.name)).call(text => text.append("tspan").attr("x", (d) => {if (d.data.name==="Survivors") {return"0.8em" }if (d.data.name==="Displaced") {return"-0.8em" }return0 }).attr("y","0.7em").attr("font-size","24px").attr("fill","#fff").text(d => d.data.value.toLocaleString("en-US")));return svg.node();}
Boycott, Divest, and Sanction
Israel has done a good job of making it impossible for federal contractors to take a stand, but the majority of people can still divest. According to the Palestinian BDS National Committee, these companies are the “top priority boycott targets of the global BDS movement”2. Ideally, they should be losing value, not gaining.
The BDS movement is asking us to completely boycott these companies. They are chosen specifically because of their complicity in apartheid.
Code
// the other chart has notes since they are nearly the same, and i made it first. this one is listed first, because BDS lists it first.consumer_boycott_chart = {// make a box to put the chart in. the aria label is for screenreaders.const container =html`<canvas id="consumer-boycott-chart" aria-label="Consumer Boycott Chart" style="width:100%;"></canvas>`;const data = consumer_boycott_stocks;const options = line_chart_options; options.plugins.title.text="Companies Targeted by the Consumer Boycotts since October 7th, 2023"// This is the chart's settings. It combines the data and scales from above before making the chart.const chart_configuration = {type:'line', data,plugins: [], options, }// build the chart and put it in the containerconst chart =newChart( container, chart_configuration);// show the chart yield container;}
Tip
There’s a lot of data! Click or tap the legend to hide lines from the chart.
Organic Boycotts
Following October 7th, this aspect of the boycott efforts occurred as businesses made announcements/took actions to support the Israeli apartheid on Palestine.
This graph measures how much their stocks have changed since October 7th. You can hover over/tap the lines to get more information.
Code
organic_boycott_chart = {// make a box to put the chart in. the aria label is for screenreaders.const container =html`<canvas id="organic-boycott-chart" aria-label="Organic Boycott Chart" style="width:100%;"></canvas>`;// Chart.js requires me to combine the configuration for the chart and the data used in the chart, and it needs them in a certain format. The formatting is at the bottom of the document.const data = organic_boycott_stocksconst options = line_chart_options options.plugins.title.text="Companies Targeted by the Organic Boycotts since October 7th, 2023"// This is the chart's settings. It combines the data and scales from above before making the chart.const chart_configuration = {type:'line', data,plugins: [ /*I cannot get plugins to work at all. This is to apply plugins just to this chart. The `Chart.register` method can be used to add plugins to all charts on the page. */ ], options, }// build the chart and put it in the containerconst chart =newChart( container, chart_configuration);// show chartyield container;}
Divestment Progress
Coming soon!
Pressure Campaign Progress
Coming soon!
Action Map
This map is meant to help you “zoom out” when considering where to plan an event. It also serves as an opportunity to be inspired by others around the world.
This map is under construction
This project now has a team of volunteers behind it. We are currently working on collecting as much event data from around the world and making sure this map can be updating daily/automatically.
Code
viewof get_gps = Inputs.button("📍 Go to my location [under construction]")gps_message =html`<span></span>`
Code
gps = { get_gps;let gps_data =nullif (navigator.geolocation) {console.log("navigator",navigator.geolocation.getCurrentPosition(got_gps_data))returnnavigator.geolocation.getCurrentPosition(got_gps_data); } else { gps_message.innerHTML=`Geolocation is not supported by this browser.`; }functiongot_gps_data (data){console.log("DATA", data.coords) gps_data = [data.coords.latitude, data.coords.longitude]return gps_data }return gps_data}
The United Nations Security Council is the only “principal organ” of the United Nations that has the ability to make decisions that Member States are obligated to fulfill under the UN charter. The other principal organs serve as places for dialogue that allow recommendations to arise.
For this reason, I will start with tracking the UNSC activity and consider adding resolution data from the other organs later.
Proposed an immediate cancellation of illegal evacuation orders made by Israel on northern Gaza and calls for humanitarian aid to be sent
Vetoed by United States and United Kingdom, vetoed because the text did not explicitly give Israel the right to self-defense (as an occupying power in Palestine, it does not currently have that right under UNSC Resolution 242).
Votes in favor - China, Gabon, Russia, United Arab Emirates.
9 abstentions from other member states
US-led draft resolution October 25, 2023
Proposed a humanitarian pause to allow aid in and stressed the “inherent right of all states” to self-defense
Vetoed by Russia and China - China said the resolution was too evasive and didn’t actually stop the fighting and does not address the root cause
No votes - United Arab Emirates
Abstentions - Brazil, Mozambique
Brazil-led draft resolution October 18
Proposed humanitarian pauses and cancellation of illegal evacuation orders by Israel
Abstentions - UK and Russia, United Kingdom wants text giving Israel the right to defend itself to be included
Votes in Favor - 12 member states
Vetoed by United States due to (1) no explicit text giving Israel new rights to self-defense (2) Biden currently visiting the area for diplomatic negotiations that were not yet done
Data
Population Counts
Code
palestinian_population = [{location:"State of Palestine - West Bank",population:2077357 }, {location:"State of Palestine - Gaza Strip",population:3086816 }, {location:"1948 Palestinian Territory",population:1634482 }]total_amount_of_palestinians_at_risk = palestinian_population.reduce(function(acc, curr){ // this little bit just adds up the populations of the different subregionsreturn acc + curr.population},0)palestinian_martyrs =11261
There are probably better ways to label palestinians_in_palestine_who_need_our_help, since many in the diaspora are losing their family members, friends, and loved ones and need help and support as they wade through grief.
This does not include the detainees and hostages Israel has taken. I am still figuring out how to track and incorporate that. Detainees from the West Bank are tracked here: https://www.pcbs.gov.ps/default.aspx.
functioncalculatePercentChanged(price_on_day, price_on_day_before =null) {// This is where this formula comes from: https://money.stackexchange.com/questions/24741/how-to-calculate-the-closing-price-percentage-change-for-a-stockif (price_on_day_before ===null) {// if it's the first day, start at 0return0 } else {return ((price_on_day - price_on_day_before) / price_on_day_before) *100 }}// This data comes from Yahoo Finance. I downloaded the CSV's and have not altered them in any way. The stock prices are combined into one lists `STOCK-GROUP_stocks` programmatically. The abbreviations are the stock tickers used in the New York Stock Exchange.consumer_boycott_stocks = ({ labels: hpq.data.map(d => d.x),datasets: [].concat.apply([], [ cspa, hpq, enrde, pumde, capa, fosuf, rmax, pep ]) });fosuf_file =awaitFileAttachment("FOSUF.csv").csv({ typed:false })fosuf = ({label:"Fosun (Owns Ahava)",data: fosuf_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: fosuf_file[i-1].Close),x: d.Date}))});rmax_file =awaitFileAttachment("RMAX.csv").csv({ typed:false })rmax = ({label:"Re/Max",data: rmax_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: rmax_file[i-1].Close),x: d.Date}))});pep_file =awaitFileAttachment("PEP.csv").csv({ typed:false })pep = ({label:"Pepsi (Owns Sabra)",data: pep_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: pep_file[i-1].Close),x: d.Date}))});enrde_file =awaitFileAttachment("ENR.DE.csv").csv({ typed:false })enrde = ({label:"Siemens",// backgroundColor: "#1682af99", // last 2 digits are opacity percentage// borderColor: "#1682af99", data: enrde_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: enrde_file[i-1].Close),x: d.Date}))});pumde_file =awaitFileAttachment("PUM.DE.csv").csv({ typed:false })pumde = ({label:"Puma",// backgroundColor: "#ac308d99",// borderColor: "#ac308d99",data: pumde_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: pumde_file[i-1].Close),x: d.Date}))});capa_file =awaitFileAttachment("CA.PA.csv").csv({ typed:false })capa = ({label:"Carrefour",// backgroundColor: "#2f2aae99",// borderColor: "#2f2aae99",data: capa_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: capa_file[i-1].Close),x: d.Date}))});cspa_file =awaitFileAttachment("CS.PA.csv").csv({ typed:false })cspa = ({label:"AXA",// backgroundColor: "#e9542099",// borderColor: "#e9542099",data: cspa_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: cspa_file[i-1].Close),x: d.Date}))});hpq_file =awaitFileAttachment("HPQ.csv").csv({ typed:false }) hpq = ({// backgroundColor: "#17b35a99",// borderColor: "#17b35a99",label:"Hewlett Packard",data: hpq_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: hpq_file[i-1].Close),x: d.Date}))});organic_boycott_stocks = ({ labels: sbux.data.map(d => d.x),datasets: [].concat.apply([], [ sbux, mcd, dpz, pzza, wix, yum]) });wix_file =awaitFileAttachment("WIX.csv").csv({ typed:false })wix = ({label:"Wix",data: wix_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: wix_file[i-1].Close),x: d.Date}))});yum_file =awaitFileAttachment("YUM.csv").csv({ typed:false })yum = ({label:"YUM (Owns Pizza Hut)",data: yum_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: yum_file[i-1].Close),x: d.Date}))});pzza_file =awaitFileAttachment("PZZA.csv").csv({ typed:false })pzza = ({label:"Papa John's",data: yum_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: pzza_file[i-1].Close),x: d.Date}))});mcd_file =awaitFileAttachment("MCD.csv").csv({ typed:false })mcd = ({label:"McDonald's",// backgroundColor: "#ac308d99",// borderColor: "#ac308d99",data: mcd_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: mcd_file[i-1].Close),x: d.Date}))});sbux_file =awaitFileAttachment("SBUX.csv").csv({ typed:false })sbux = ({label:"Starbucks",// backgroundColor: "#17b35a99",// borderColor: "#17b35a99",data: sbux_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: sbux_file[i-1].Close),x: d.Date}))});dpz_file =awaitFileAttachment("DPZ.csv").csv({ typed:false })dpz = ({label:"Domino's",// backgroundColor: "#e9542099",// borderColor: "#e9542099",data: dpz_file.map((d, i) => ({y:calculatePercentChanged(d.Close, i ===0?null: dpz_file[i-1].Close),x: d.Date}))});
Download the data
Map Data
How the data is processed
Code
map_data =awaitFileAttachment("map.csv").csv({typed:true})// if it doesn't have a date, it's a location ()key_locations = map_data.filter(marker => {return!marker.Date&& marker.Icon!=="redIcon"})// this filters the map data to remove the items that don't have geolocation datamarker_data = map_data.filter(marker => {return!!marker.Latitude})
Download the data
Tools
Note
These tools are from other places on the internet, which is why the download buttons are not available here.
This is used to create the stock chart.
Code
// For devs, you'll need these docs to understand why I am using this format just to import chartjs and its plugins// https://observablehq.com/documentation/debugging/require-stubborn-modules#requiring-stubborn-add-ons// This tool is also kinda helpful: https://observablehq.com/@observablehq/module-require-debuggerChart = {const Chart = (window.Chart=awaitrequire("chart.js@4.4.0/dist/chart.umd.js"));// if I can get any of the plugins to work, they would be imported here.return Chart}
This is used to configure the chart.
Code
stock_chart_scales= ({y: {title: {display:true,text:"Change in stock price" },ticks: {callback: d =>`${d}%`, }, },x: {title: {display:true,text:"Date" }, } }); line_chart_options = ({tension:0.4,// makes the lines kind of curvy and less pointyscales: stock_chart_scales,// determine how the x and y axes workmaintainAspectRatio:false,// allows graph to grow tall for mobile devicesplugins: { // these plugins are built-intitle: {display:true,text:""// this is set later, depending on the graph that uses it },subtitle: {display:true,text:"Percent Change in Stock Value Over Time" },tooltip: {// I can render notes that explain more context for the dates here// https://www.chartjs.org/docs/latest/configuration/tooltip.html#tooltip-callbackscallbacks: {// adds $ to values. From the docs: https://www.chartjs.org/docs/latest/configuration/tooltip.html#label-callbacklabel:function(context) {// Chart.js uses this function and tells us when they use it, they give it this context data. let label = context.dataset.label||'';if (label) { label +=': '; }if (context.parsed.y!==null) {// format euroif (label ==="Carrefour: "|| label ==="AXA: "|| label ==="Siemens: "|| label ==="Puma: " ){ label +=newIntl.NumberFormat('en-US', { style:'currency',currency:'EUR' }).format(context.parsed.y); } elseif (label ==="Fosun (Owns Ahava): "){// format hkd label +=newIntl.NumberFormat('en-US', { style:'currency',currency:'HKD' }).format(context.parsed.y); } else {// format usd label +=newIntl.NumberFormat('en-US', { style:'currency',currency:'USD' }).format(context.parsed.y); } }return label; } } } } })
This is used to make the map.
Code
L =require("leaflet")heatLayer =require('leaflet.heat').catch(() => L.heatLayer)
Footnotes
My data on Palestinian populations comes from the Palestinian Central Bureau of Statistics. Their data doesn’t account for the overlap occuring between measurements (e.g. people who are displaced and injured), repeat experiences (people who’ve been displaced more than once), or how injury is being measured (the inclusion/exclusion or overlapping data regarding illnesses). Additionally, some data is still coming out. For example, there were 5,500 patients in the maternity ward of Al-Shifa hospital expecting to give birth in October 2023 who were transferred to al-Helou hospital, and I don’t know of their current status. Several western countries like the United States and Italy refuse to affirm or investigate these numbers, though the UN has confirmed the Palestinian Authority’s accuracy in reporting deaths in the past. As a result, I am limited in the data sources I can find.↩︎
The emphasis is mine. The Palestinian BDS National Committee is a group of Palestinian organizations that leads the BDS movement. You can learn more at https://bdsmovement.net.↩︎