Navigating the Ethical Implications of AI in Content Creation: Lessons from the Organic Food Industry

Disclaimer: This article was developed based on a human-generated concept using Artificial Intelligence technologies. In compliance with our commitment to responsible AI use and transparency, and in anticipation of sadly non-existent UN regulations, we inform our readers about AI’s involvement in creating this content. Please note that the consumption of AI-generated content and its potential long-term effects on mental health remain understudied. We advise you to exercise caution and prioritize your well-being when engaging with AI-generated content.

Introduction:

As AI systems like ChatGPT become more advanced and integrated into our daily lives, it’s crucial to consider the ethical implications of their widespread use, particularly on human content creators and their motivations. One way to approach this issue is by drawing parallels with the organic food industry and the GDPR. In this article, we’ll explore the analogy between organic vs heavily processed food and discuss strategies to promote responsible AI use in content creation.

Organic vs Heavily Processed Content:

Organic content, like organic food, is created by humans and reflects human experiences, emotions, and creativity. Heavily processed content, on the other hand, is AI-generated or modified, deriving from large datasets and potentially lacking the authenticity of human-created content. Just as we prioritize the freshness and health benefits of organic food, it’s essential to recognize the value of organic content in fostering genuine human connections and creativity.

Lessons from the Food Industry and GDPR:

  1. Clear labeling: Indicate when content is AI-generated or modified, allowing consumers to make informed decisions about the content they consume.
  2. Certification process: Establish a certification process for AI-generated content to ensure it meets specific quality and ethical standards.
  3. Responsible consumption: Promote awareness of the benefits and potential drawbacks of AI-generated content to foster a culture of responsible consumption and informed decision-making.
  4. Support for “organic” content: Celebrate human creators and recognize the value they bring to society, ensuring their contributions are not overshadowed by AI-generated content.
  5. Regulation and oversight: Implement policies and regulatory frameworks to ensure responsible AI development and use while supporting transparency, accountability, and public trust.

Strategies for a Balanced AI-Content Ecosystem:

  1. Develop mechanisms to recognize and credit original content creators when AI is used to adapt or build upon their work.
  2. Invest in research focusing on AI’s potential to augment human creativity and problem-solving, rather than replacing it entirely.
  3. Enforce AI model development in a way that preserves and credits the source of data/content below each generated content.
  4. Implement ways to verify if a particular creator’s content was used to train a model or not, and provide options for content creators to opt-out, so their content will be removed from training data and “erased” from the model.
  5. Establish ethical guidelines for AI development and use, ensuring it serves humanity’s best interests and respects people’s rights.

Conclusion:

Navigating the ethical implications of AI in content creation requires thoughtful consideration, open discussion, and collaboration between AI developers, policymakers, and society. By learning from the organic food industry and the GDPR, and implementing similar strategies, we can create an environment that encourages responsible AI use while preserving the importance of human creativity and connection.

PlanZed – welcome to my new project

Hi all, it’s a pity I have no time to share my discovering here more often, and I have so much of them!
I finished developing first phase of PlanZed project – cloud mind map app and it’s now available for free!
Welcome to try the app, I need feedback so much.
Here is in brief what do we have under the hood:

  • React – Next.js
  • Material UI as basement + I had to implement a lot of specific widgets
  • Hosted on Vercel
  • Mongo as a DB
  • No I don’t use jsPlumb – svg arrows are hand-written.
  • Pan/Zoom also hand-written (will have a separate post about this, don’t use css translate for pan, let browser scroll it for you, it’s much better!)
  • Built-in custom icons search from materialdesignicons.com
  • I extented already existing in materialdesignicons keywords by synonyms using wordnet (thanks for ‘node-wordnet’)
  • The project is currently trying to support three languages, same icons search do – icons keywords, extend by synonyms, are translated using Google API
  • No images upload – it will cost for me to store / manage them, so later as a paid option
  • But there is built-in search of free photos from pexels.com – in most cases that’s enough for mind map(they already support localized search)

Here is a GitHub repo serving as a community for PlanZed

Several screenshots from the project:

How to make “Fire-and-forget” HTTP request from Node.js without waiting for response.

Lambda functions at zeit/now v2 platform have default execution timeout 10s.
Another limitation important to consider:
– the process gets killed right after the response was sent.
When you have a job to be done in the lambda, not required immediately to prepare HTTP response (e.g. writing log to the database)
– it’s a good idea to send a response to the client ASAP and do rest of the work “in the background”.
But there are no built-in ways to do such things in zeit/now v2 lambdas.

The only way everybody talks but nobody tried is to extract such task into separate lambda and call it via another HTTP request.
It sounds easy, but let’s take a closer look:

  • λ1 was asked for a response. If it says anything – the game is over, no job can be done anymore.
  • so let λ1 ask λ2 to do rest of the job
  • and then respond to the original request

Ok?
How to ask λ2 to do something?

  1. make a request → get 200OK → respond
    get 200OK? λ2 is lambda too. It can’t say anything until the job is finished or it will be killed.
    So λ1 still have to wait for all the job is done before responding.

  2. make a request → “let it go” → respond
    How to let it go? Don’t pass any callback, don’t wait for any promise.
    But this way response to the original request will be sent too soon and λ1 will be killed.
    λ2 will have no chance to receive a request, parse args and perform any action.

  3. Make sure λ2 got the request, but don’t wait for a response:

//delegate.js
const https = require('https');
module.exports = async (host, message) => {
  message = JSON.stringify(message);
  var options = {
    hostname: host,
    method: 'POST',
    path: '/lambda2',
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(message),
    },
  };
  await new Promise((resolve, reject) => {
    let req = https.request(options);
    req.on('error', (e) => {
      console.error(`problem with request: ${e.message}`);
      reject(e);
    });
    req.write(message);
    req.end(() => {
      console.log("NOW it's not λ1's problem anymore.");
      resolve();
    });
  });
};
//lambda1.js
const delegate = require('./delegate');
module.exports = async (req, res) => {
  let host = req.headers['x-now-deployment-url'];
  await delegate(host,'Hey, you, λ2! Did you hear an order?');
  res.end('Yes, my Master! Will be done!')
};
//lambda2.js
module.exports = async (req, res) => {
  console.log("Oh, so much to be done! Let's start immediately!");
  await new Promise((resolve) => {
    setTimeout(() => {
      console.log('Nothing was done successfully!');
      send(res, 200);
      resolve();
    }, 2000);
  });
};

Hope this piece of code will save someone’s work day!

Deep filter js object or array with Lodash extension

This tutorial is outdated, I still need to find some time to update it to the most recent deepdash version.

New sample comments app re-written with React and Redux is ready.

Here is a sandbox with it. And here is GitHub repo

The updated tutorial is coming soon…

Below is an old version of the text with updated versions of Codepens


Lodash method “_.filter” iterates over the top level only. If you want to filter nested data – “_.filterDeep” from the Deepdash will do the job.


Deepdash is an extension for the Lodash library. It mixes several traversal methods right into Lodash object and tries to follow API style of this great library. So if you are familiar with Lodash, you will feel ok with Deepdash too. 


This little tutorial will let you see, how to process tree-like structures easily.

We will build this simple comments app:

See the Pen Deepdash filter deep tutorial by Yuri Gor (@yurigor) on CodePen.0

And here is our: starting point – everything is ready, but filters still don’t work.
Let’s fork it and start to build filters one by one.

Take a look in the code:

  • getData – this function (at the bottom of the script) serves as a sample data source. It returns a tree-like array of comments with nested replies and sub-replies and.. you know, some more replies inside.
  • filterState – the object which accumulates state for all filters we will change.
  • filterComments – the main function in our tutorial. Now it just returns unfiltered data.
  • This Pen already configured to load Lodash and Deepdash libraries. Check Settings / JavaScript to see how.
  • Rest of the code is out of the scope of our tutorial. It’s pretty straightforward, and if you are interested and something is not clear for you – feel free to ask in the comments.

Let’s start coding!

First, we will make to work “Verified” filter. When the user switches it on, we set the field “filterState.verified” of the global “filterState” object to “true”.
All the comments will be re-rendered after that, so our task is to check “filterState” object if it has “verified” flag and to filter data accordingly. Let’s go to the “filterComments” function:

function filterComments(){
  var data = getData();
  var filtrate = data;
  if(!_.isEmpty(filterState)){     
     //will do filtering here
  }  
  
  return data;
} 

Our first check is Lodash’s function “_.isEmpty”. When the filter is off, the app does not set the flag to the “false” value, but removes the corresponding field from the state object with “delete”:

if(event.target.checked)
  filterState[id] = true;
else
  delete filterState[id];

This way we will have an empty “filterState” object in case of all the filters are off. Let’s check explicitly if the “verified” filter is on. If so – let’s filter data:

function filterComments(){
  var data = getData();
  var filtrate = data;
  if(!_.isEmpty(filterState)){
     if(filterState.verified){
      filtrate = _.filterDeep(filtrate,function(value,key){
        return key == 'verified' && value;
      });
    }
    return filtrate;
  }
  return data;
}

As you see, “filterDeep” function has source object and a callback function as arguments. The callback will be called for each node in the tree. It receives current value, field name, path, and parent arguments. Check docs for details.

Right now we are interested in the first two args: “key”, which should be ‘verified’ and “value”- it should equal “true”.
If criteria fulfilled – we return “true”. Else we will return “false”.

Note that in general, it’s a huge difference between returning “false” explicitly and returning “undefined”.
By default, “_.filterDeep” function invokes callback for “leaves” only. A node is considered “leaf” if it has no children.
But you can set “leafsOnly” option to “false” and in this case returning “false” explicitly will prevent iteration over the current node’s children.

Now try to click “Verified” filter…

It moves, but it’s not alive yet. Let’s see what’s happening. Add console.log(filtrate); right before first return statement:

[
  {
    "verified": true,
    "replies": [
      {
        "verified": true,
        "replies": [
          {
            "verified": true,
            "replies": [
              {
                "verified": true
              }
            ]
          }
        ]
      },
      {
        "verified": true
      }
    ]
  },
  {
    "replies": [
      {
        "verified": true,
        "replies": [
          {
            "replies": [
              {
                "verified": true
              }
            ]
          }
        ]
      }
    ]
  },
  {
    "replies": [
      {
        "verified": true
      }
    ]
  },
  {
    "verified": true
  }
]

We have exactly what we’ve asked for: all the “verified” fields with “true” value and all their parents. But we are missing the rest of the data, let’s add it with a second pass.

Now we need to collect all the leaves of each comment we have in the “filtrate” object, both of verified author and for the parent comments, even if it’s not written by a verified author. So let’s check each leaf in the source data object, and if its parent node exists in the previously collected “filtrate” object, then we need it:

function filterComments(){
  var data = getData();
  var filtrate = data;
  if(!_.isEmpty(filterState)){
     if(filterState.verified){
      filtrate = _.filterDeep(filtrate,function(value,key){
        return key == 'verified' && value;
      });
    }
    return _.filterDeep(data,function(value,key,path,depth,parent,parentKey,parentPath){
      return _.has(filtrate,parentPath);
    });
  }
  return data;
}

Let’s try it!

Now we have missing data, but something went wrong with the filter itself. There are unverified users still visible, while we are missing some correct nodes. Let’s debug.

Addconsole.log(_.indexate(data));
and console.log(_.indexate(filtrate));
before return filtered data.
“_.indexate” is another function from Deepdash, it builds flat “index” of the object, where keys are path strings and values are corresponding values. This method by default collect leaves only too.
So here are our indexes:

// data
{
  "[0].name": "Bob",
  "[0].text": "Perfect!",
  "[0].rating": 5,
  "[0].verified": true,
  "[0].replies[0].name": "Alice",
  "[0].replies[0].text": "Agree",
  "[0].replies[0].verified": false,
  "[0].replies[1].name": "admin",
  "[0].replies[1].text": "Thank you!",
  "[0].replies[1].verified": true,
  "[0].replies[1].replies[0].name": "Bob",
  "[0].replies[1].replies[0].text": "Write more!",
  "[0].replies[1].replies[0].verified": true,
  "[0].replies[1].replies[0].replies[0].name": "admin",
  "[0].replies[1].replies[0].replies[0].text": "Ok :)",
  "[0].replies[1].replies[0].replies[0].verified": true,
  "[0].replies[2].name": "Augusta",
  "[0].replies[2].text": "like a brillaint!11",
  "[0].replies[2].verified": true,
  "[1].name": "mr.John",
  "[1].text": "Well done..",
  "[1].rating": 4,
  "[1].verified": false,
  "[1].replies[0].name": "admin",
  "[1].replies[0].text": "Can it be better?",
  "[1].replies[0].verified": true,
  "[1].replies[0].replies[0].name": "mr.John",
  "[1].replies[0].replies[0].text": "May be last three lines can be shorter..",
  "[1].replies[0].replies[0].verified": false,
  "[1].replies[0].replies[0].replies[1].name": "Bob",
  "[1].replies[0].replies[0].replies[1].verified": true,
  "[1].replies[0].replies[0].replies[1].text": "Don't listen to him, it will be unreadable!",
  "[2].name": "Mark",
  "[2].rating": 5,
  "[2].text": "Any way to donate?",
  "[2].verified": false,
  "[2].replies[0].name": "Bill",
  "[2].replies[0].text": "+1",
  "[2].replies[0].verified": false,
  "[2].replies[1].name": "Larry",
  "[2].replies[1].text": "+1",
  "[2].replies[1].verified": true,
  "[3].name": "Regina",
  "[3].rating": 2,
  "[3].text": "Not really like it",
  "[3].verified": true,
  "[3].replies[0].name": "admin",
  "[3].replies[0].text": ":(",
  "[3].replies[0].verified": true
}
// filtrate
{
  "[0].verified": true,
  "[0].replies[0].verified": true,
  "[0].replies[0].replies[0].verified": true,
  "[0].replies[0].replies[0].replies[0].verified": true,
  "[0].replies[1].verified": true,
  "[1].replies[0].verified": true,
  "[1].replies[0].replies[0].replies[0].verified": true,
  "[2].replies[0].verified": true,
  "[3].verified": true
}

What’s wrong? We still see how Alice said “Agree”, but Alice is not the verified user. We can see in the data index: [0].replies[0].verified": false

let’s check what do we have in our filtrate index: [0].replies[0].verified": true. Why?

No, this is not Deepdash bug (but once it almost used to be). To understand what’s happening here, we should know a bit how deep filtering works.


Deepdash iterates over each path of the source object, and if the callback function says “true” it copies the current value to the result object by the exact same path.
This works fine, but result object may start to have sparse arrays if some element was rejected by the callback function. When some array’s element is missing, its place marked as “empty”.
In most cases, developers will not need such sparse arrays as a result, so by default “_.filterDeep” function applies _.condenseDeep function to the result object before return it.
Condensing array means removing empty slots from it. The array will be re-indexed, and values of such condensed array will be placed to the shifted positions with changed indexes.


This is a reason why we have incorrect comments tree – filtrate object contains mutated paths, so its paths do not correspond with source object anymore.

To avoid this situation, we can disable auto-condensing by passing option condense: false

The second issue in our code is Lodash “_.has” function.
It doesn’t consider the possibility of a sparse array and returns “true” for empty array slots. While Deepdash “_.exists” alternative function will work as we need.

function filterComments(){
  var data = getData();
  var filtrate = data;
  if(!_.isEmpty(filterState)){
     if(filterState.verified){
      filtrate = _.filterDeep(filtrate,function(value,key){
        return key == 'verified' && value;
      },{condense:false});
    }
   
    return _.filterDeep(data,function(value,key,path,depth,parent,parentKey,parentPath){
      return _.exists(filtrate,parentPath);
    });
  }
  return data;
}

Here is our working demo:

See the Pen Deepdash filter deep tutorial – verified filter by Yuri Gor (@yurigor) on CodePen.0

Filtering by keyword

Next, let’s implement text filter. You may already have noticed, that entering text into the filter field will highlight matching symbols in the comments.
But we still need to hide comments, where no keywords were found.

Currently, we use “verified” fields as key points in the result “filtrate” object. But now we need to consider another field “text”, and in case of both “verified” and “text” filters are active, we must consider them both at the same time. Semantically “text” is the most important field in the comments data. So let’s limit our callback to respond only to iterations over “text” field, and all the other fields will be checked via “parent” calback’s argument.

function filterComments(){
  var data = getData();
  var filtrate = data;
  if(!_.isEmpty(filterState)){
      filtrate = _.filterDeep(filtrate,function(value,key,path,depth,parent){
        if(key!=='text')
          return;
        var res = true;
        if(filterState.verified){
          res = res && parent.verified;
        }
        return res;
      },{condense:false});
    return _.filterDeep(data,function(value,key,path,depth,parent,parentKey,parentPath){
      return _.exists(filtrate,parentPath);
    });
  }
  return data;
}

Make sure “Verified” filter is still working.
Now it’s easy to add filtering by the second field.
If “filterState.text” exists, we check if the keyword is a substring of “text” field’s value:

function filterComments(){
  var data = getData();
  var filtrate = data;
  if(!_.isEmpty(filterState)){
     
      filtrate = _.filterDeep(filtrate,function(value,key,path,depth,parent){
        if(key!=='text')
          return;
        var res = true;
        if(filterState.verified){
          res = res && parent.verified;
        }
        if(filterState.text){
          res = res && _.includes(value.toLowerCase(),filterState.text);
        }
        return res;
      },{condense:false});
    
    return _.filterDeep(data,function(value,key,path,depth,parent,parentKey,parentPath){
      return _.exists(filtrate,parentPath);
    });
  }
  return data;
}
It works!

The last filter to build – is the filter by stars count. User can exclude comments with specific rating by pressing corresponding star button in the filter panel. Each star button will set “true” in the “filterState.s1..s5″ field.
Since we have rating on top level comments only, we don’t need deep filtering here, just Lodash will be enough.

First, we collect all the pressed star buttons to the array:

  var excludeStars = _.reduce(filterState,function(r,v,k){
    if(k.length==2&&k[0]=='s')r.push(parseInt(k[1]));
    return r;
  },[]);

Then, we remove comments with rating user wants to exclude:

  _.remove(data,function(v){return _.includes(excludeStars,v.rating)});

Finally we have:

function filterComments(){
  var data = getData();
  var excludeStars = _.reduce(filterState,function(r,v,k){
    if(k.length==2&&k[0]=='s')r.push(parseInt(k[1]));
    return r;
  },[]);
  _.remove(data,function(v){return _.includes(excludeStars,v.rating)});
  var filtrate = data;
  if(!_.isEmpty(filterState)){
     
      filtrate = _.filterDeep(filtrate,function(value,key,path,depth,parent){
        if(key!=='text')
          return;
        var res = true;
        if(filterState.verified){
          res = res && parent.verified;
        }
        if(filterState.text){
          res = res && _.includes(value.toLowerCase(),filterState.text);
        }
        return res;
      },{condense:false});
    
    return _.filterDeep(data,function(value,key,path,depth,parent,parentKey,parentPath){
      return _.exists(filtrate,parentPath);
    });
  }
  return data;
}

That’s it. All the filters work as we need. Here is a link to the final codepen

Any feedback or questions are highly appreciated.

How to instantiate a child class from static method of parent class in javascript es6

I looked for the way to get current class in static method of the base class,
to create the correct instance, depending on actual class, this static method was called on, but found nothing.
Luckily I discovered, that ‘this’ is a constructor but not an instance in a static method,
so you can just call it with ‘new’, in case you want something like factory:

class Base {
  hello(){
    return `Hello from ${this.constructor.name}!`;
  }
  static getInstance(){
    console.log(this.name + ' creating..');
    return new this();
  }
}

class Child extends Base {}

console.log(Base.getInstance().hello());
console.log(Child.getInstance().hello());

Console output:
Base creating..
Hello from Base!
Child creating..
Hello from Child!

(Tested in the nodejs v8.6.0)

Show hidden part of truncated text on hover/touch

Here are three ways to show hidden part of truncated text on touch/hover without Javascript

1) overflow:visible

Hover over or touch me to see the full version of this string. It looks like replaced by tooltip
some other stuff

Pros:

  • No Javascript
  • Good readability
  • Small and simple cross-browser CSS code
  • Easy to render.

Contras:

  • Need extra markup (inner span wrapper)
  • Requires all the parents of the text container to be wide enough or have ‘overflow’ porperty to be ‘visible’.

Here is SCSS with comments:

.overflow-tip {
  /* make it single-line */
  white-space:nowrap;
  /* truncate by container's size */
  overflow-x: hidden;
  /* add three dots */
  text-overflow: ellipsis;
  /* on touch or hover */
  &:active, &:hover {
    /* show hidden part outside of parent */
    overflow-x: visible;
    /* and with inner span */
    span {
      /* allow to overlap siblings */
      position: relative;
      /* make readable design */
      background-color: PaleGoldenRod;
      border: 1px solid gray;
      padding: 3px;
      /* compensate padding and border size to avoid jerking */
      margin-left: -4px;
    }    
  }
}

2) direction:rtl

Hover over or touch me to see the end of this long string.
some other stuff

Pros:

  • No Javascript
  • No extra HTML
  • Super simple CSS

Contras:

  • Not so readable for very long string or very short container
    Commented scss:
.rtl-switch {
  /* make it single-line */
  white-space:nowrap;
  /* truncate by container's size */
  overflow-x: hidden;
  /* add three dots */
  text-overflow: ellipsis;
  /* on touch or hover */
  &:active, &:hover {
    /* make text have right to left direction */
    direction: rtl;
    /* fix three dots overlapping issue*/
    padding-left:10px;
    &:after {
      /* fix brackets in rtl mode */
      content: "\200E‎";
    }
  }
}

3) marquee (transition left)

Hover over or touch me to see animated scrolling of this string. Fancy but buggy. May be still can be improved.
some other stuff

Pros:

1) Fancy
2) Displays whole string in a limited space

Contras:

1) Bloated HTML (needs two extra spans)
2) Bad readability for long strings
3) Animation bug on mouse out or not truncated string(may be still can be fixed, I’ll see later)
4) Animation can load CPU on heavy page
5) Needs hardcoded offset, or will be scrolled until container is not empty
Let’s see some code:

.marquee {
  /* Single line */
  white-space:nowrap;
  /* Truncate by container size */
  overflow: hidden;
  /* Both spans */
  span {
    /* Allow to set size */
    display: inline-block;
    /* Size same as container when not hovered to allow ellipsis */
    width: 100%;
    /* Second span */
    span {
      /* Turn animation on */
      @include transition(left 4s linear);
      /* Allow position manipulation */
      position: relative;
      /* truncate by container's size */
      overflow-x: hidden;
      /* add three dots */
      text-overflow: ellipsis;
      /* Explicitly declare initial position to animate well */
      left: 0px;
    }   
  }
  /* on touch or hover */
  &:active, &:hover {
    /* Both spans */
    span {
      /* resize to contain whole string without truncation. */
      width: auto;
      /* Second span */
      span {
        /* Animated scroll by length of first span (100% of parent),
  which equals to length of string, not a container.
  Also shift final point back by width of container (500px)
  to not finish with empty box
  and pitch by 15px - I don't know why, may be to compensate paddings */
        left:calc(500px - 15px - 100%);//
      }
    }    
  }

Here is a codepen to play:

See the Pen Expand cropped string on hover/touch, pure CSS by Yuri Gor (@yurigor) on CodePen.0

Pan and Zoom in jsPlumb Community Edition with Dagre and jQueryUI Draggable

See the Pen Pan and Zoom in jsPlumb Community Edition with Dagre and jQueryUI Draggable by Yuri Gor (@yurigor) on CodePen.0

Chart with draggable HTML elements as nodes, connected by jsPlumb library.
“Pan&Zoom” feature missing in Comunity Edition implemented by using “jQuery Panzoom” plugin.
Nodes dragging implemented by jQueryUI Draggable, to compensate scale distortion.
Dagre layout library used for demonstration.

I use static predefined html in this example:

<!-- .container - just part of your page,
where you want to render diagram -->
<div class="container">
  <!-- .panzoom - wrapper div, panzoom plugin will transform it.
        Use it for worksheet element styling -->
  <div class="panzoom">
    <!-- .diagram - wrapper div to be used by jsPlumb.
         It will have zero height, so no visual CSS works here. -->
    <div class="diagram">
      <!-- .item - diagram nodes, must have unique id's
           to be able connect them by jsPlumb. -->
      <div id="i0"  class="item">Root</div>
      <div id="i1" class="item">Child 1</div>
      <div  id="i11" class="item">Child 1.1</div>
      <div  id="i12" class="item">Child 1.2</div>
      <div id="i2" class="item">Child 2</div>
      <div id="i21" class="item">Child 2.1</div>
      <div id="i3" class="item">Child 3</div>
    </div>
  </div>
</div>

Links between nodes declared in js array:

var links = [
  { from: "i0", to: "i1" },
  { from: "i1", to: "i11" },
  { from: "i1", to: "i12" },
  { from: "i0", to: "i2" },
  { from: "i2", to: "i21" },
  { from: "i0", to: "i3" },
];

Initializing of panzoom:

$panzoom = $container.find('.panzoom').panzoom({
      minScale: minScale,//0.4
      maxScale: maxScale,//2
      increment: incScale,//0.1
      cursor: "",/*empty string prevents panzoom
          from changing cursor styles defined in your css.*/
    }).on("panzoomstart",function(e,pz,ev){
      $panzoom.css("cursor","move");//set "move" cursor on start only
    })
    .on("panzoomend",function(e,pz){
      $panzoom.css("cursor","");//restore cursor
    });

Mouse wheel support and pan while drag begins outside diagram:

$panzoom.parent()
    .on('mousewheel.focal', function( e ) {
      //if Control pressed then zoom
      if(e.ctrlKey||e.originalEvent.ctrlKey)
      {
        e.preventDefault();
        var delta = e.delta || e.originalEvent.wheelDelta;
        var zoomOut = delta ? delta < 0 : e.originalEvent.deltaY > 0;
        $panzoom.panzoom('zoom', zoomOut, {
           animate: true,
           exponential: false,
        });
      }else{//else pan (touchpad and Shift key works)
        e.preventDefault();
        var deltaY = e.deltaY || e.originalEvent.wheelDeltaY || (-e.originalEvent.deltaY);
        var deltaX = e.deltaX || e.originalEvent.wheelDeltaX || (-e.originalEvent.deltaX);
        $panzoom.panzoom("pan",deltaX/2,deltaY/2,{
          animate: true,
          relative: true,
        });
      }
    })
    //on start store initial offsets and mouse coord
    .on("mousedown touchstart",function(ev){
      var matrix = $container.find(".panzoom").panzoom("getMatrix");
      var offsetX = matrix[4];
      var offsetY = matrix[5];
      var dragstart = {x:ev.pageX,y:ev.pageY,dx:offsetX,dy:offsetY};
      $(ev.target).css("cursor","move");
      $(this).data('dragstart', dragstart);
    })
    //calculate mouse offset from starting pos and apply it to panzoom matrix
    .on("mousemove touchmove", function(ev){
      var dragstart = $(this).data('dragstart');
      if(dragstart)
      {
        var deltaX = dragstart.x-ev.pageX;
        var deltaY = dragstart.y-ev.pageY;
        var matrix = $container.find(".panzoom").panzoom("getMatrix");
        matrix[4] = parseInt(dragstart.dx)-deltaX;
        matrix[5] = parseInt(dragstart.dy)-deltaY;
        $container.find(".panzoom").panzoom("setMatrix",matrix);
      }
    })
    .on("mouseup touchend touchcancel", function(ev){
      $(this).data('dragstart',null);
      $(ev.target).css("cursor","");
    });
  });

Make nodes draggable by jQueryUI/draggable:

var currentScale = 1;
  $container.find(".diagram .item").draggable({
    start: function(e){
      var pz = $container.find(".panzoom");
      //save current scale factor to consider it later
      currentScale = pz.panzoom("getMatrix")[0];
      $(this).css("cursor","move");
      //disable panzoom, to avoid panning while dragging node
      pz.panzoom("disable");
    },
    drag:function(e,ui){
      /*compensate current scale while dragging,
           else pointer and node will have different speeds*/
      ui.position.left = ui.position.left/currentScale;
      ui.position.top = ui.position.top/currentScale;
      //it's possible to have not connected nodes, so let's check it.
      if($(this).hasClass("jsplumb-connected"))
      {
        plumb.repaint($(this).attr('id'),ui.position);
      }
    },
    stop: function(e,ui){
      var nodeId = $(this).attr('id');
      if($(this).hasClass("jsplumb-connected"))
      {
        plumb.repaint(nodeId,ui.position);
      }
      $(this).css("cursor","");
      $container.find(".panzoom").panzoom("enable");
    }
  });

Pure (S)CSS Responsive Loading Indicator (Spinner)

Hi!
I used to use a lot of spinners, really.
Animated gifs, JS-based, mixed HTML+CSS, inline, full-page.
Finally I made one myself, to meet this requirements:
– No more animated GIF (it’s 2016)
– No images at all
– No extra HTML
– No extra HTML added by JS
– because no JS too
– No size adjusting – it should work with any given parent HTML element
– optionally disable target content by semitransparent mask
– No unnecessary blinking in case of background process fast finish
– Visible on any background
– looking good

So meet my Busy Indicator:

See the Pen responsive-scss-spinner by Yuri Gor (@yurigor) on CodePen.dark

It’s SCSS based, so feel free to adjust it for your taste.

Core techniques used to create it:
CSS3 animation used to rotate :before and :after pseudo-elements with border-radius 50%, transparent top and bottom borders and animated color of left and right borders.
Delay and smooth fade in effect made by animation of opacity and visibility properties.
Padding-bottom with percentage value used to keep width/height ratio when resizing.
Both :before and :after elements used, so mask implemented by box-shadow.
user-select and pointer-events css properties set to none, but there is still possibility to set focus on parent element by TAB key, please, handle it by yourself if you care.