Skip to content

Instantly share code, notes, and snippets.

@Kcnarf
Last active December 3, 2018 08:19
Show Gist options
  • Save Kcnarf/c82662f8d34d2fd6b1ba5312fe682ddc to your computer and use it in GitHub Desktop.
Save Kcnarf/c82662f8d34d2fd6b1ba5312fe682ddc to your computer and use it in GitHub Desktop.
extended mirror barchart
license: mit

Sketching mirror graph with extra information. In each row:

  • name identifies a particular theme
  • colored bars represent negative and positive sentiment values | percentages
  • tiny grey bars (above colored bars): negative and positive sentiment percentages of a reference (here, considering country values)
  • right-to-bar values: positive sentiment percentage difference with regards to reference
  • left-to-bar value: negative sentiment percentage difference with regards to reference
  • left most value: total percentage difference with regards to reference

Acknowledgments to:

stage concept positive negative positiveFrance negativeFrance occurence total
Total Total 0 0 0 0 0 141
RDV 1- Facilité à joindre la concession 0 0 0 -1 0,0% 0
RDV 2- Facilité à prendre un RDV 6 -3 3 -1 2,9% 9
ACCESSIBILITE 3- Parking client / accès 0 -2 0 -5 0,6% 2
ACCESSIBILITE 4- Horaires d'ouverture 0 -1 0 -2 0,3% 1
ACCUEIL 5- Convivialité 83 0 85 -1 26,8% 83
ACCUEIL 6- Accueil personnalisé 3 -1 5 -1 1,3% 4
DEPOSE VEHICULE 7- Personnel disponible 9 -3 13 -3 3,9% 12
DEPOSE VEHICULE 8- Ecoute 6 0 7 0 1,9% 6
DEPOSE VEHICULE 9- Compétence du personnel 31 0 29 -1 10,0% 31
DEPOSE VEHICULE 10- Confort salon client / Attente 1 -5 3 -4 1,9% 6
INTERVENTION 11- Qualité/Disponibilité VK/Mobilité 2 -3 3 -3 1,6% 5
INTERVENTION 12- Intervention rapide 5 0 9 0 1,6% 5
INTERVENTION 13-Informations/ Respect des délais 23 -2 16 -1 8,1% 25
INTERVENTION 14- Qualité de l'intervention 17 -1 13 -4 5,8% 18
RESTITUTION 15- Lavage 7 -1 6 -3 2,6% 8
RESTITUTION 16- Ensemble des demandes faites 1 -3 1 -1 1,3% 4
RESTITUTION 17- Explication des travaux 8 0 7 -2 2,6% 8
FACTURATION 18- Facture claire 0 0 0 0 0,0% 0
FACTURATION 19- Respect/pésentation du devis 1 0 1 -1 0,3% 1
FACTURATION 20- Tarifs compétitifs 1 -3 2 -10 1,3% 4
AUTRES 21- Qualité locaux 3 -4 1 -1 2,3% 7
AUTRES 22- Suivi 7 -2 9 -4 2,9% 9
AUTRES 23- Prise en charge garantie/Gestes Commerciaux 3 0 2 -1 1,0% 3
AUTRES 24- Service satisfaisant 41 0 27 0 13,2% 41
AUTRES 25- Service parfait / irréprochable 11 0 20 0 3,5% 11
AUTRES 26- J'ai confiance 7 0 6 -1 2,3% 7
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<svg>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
//begin: data conf.
var data = [],
min = Infinity,
max = -Infinity,
totalCount = 0;
//end: data conf.
//begin: layout conf.
var svgWidth = 960,
svgHeight,
deltaWidth = 50,
conceptNameWidth = 360,
countWidth = 60,
chartWidth = svgWidth - 3*deltaWidth - conceptNameWidth - 2*countWidth,
deltaTotalX = 0,
conceptNameX = deltaTotalX+deltaWidth,
deltaNegativeX = conceptNameX+conceptNameWidth,
chartX = deltaNegativeX+deltaWidth+40,
deltaPositiveX = svgWidth-deltaWidth,
bandHeight = 20,
barPaddingTop = 2,
barReferenceHeight = 4,
barHeight = bandHeight-2*barPaddingTop-barReferenceHeight,
textY = 14;
//end: layout conf.
//begin: hack stuff
var currentStage,
currentStageIndex;
//end: hack stuff
//begin: scales
var y = d3.scaleBand();
var x = d3.scaleLinear();
//end: scales
//begin: reusable d3-selections
var svg = d3.select("svg");
//end: reusable d3-selections
d3.csv("apv.csv", parser, function(error, parsedData) {
svgHeight = bandHeight*data.length;
svg.attr("width", svgWidth)
.attr("height", svgHeight);
x.range([0, chartWidth])
.domain([min, max]);
y.range([0, svgHeight])
.domain(data.map(function(d){ return d.id; }));
var bands = svg.selectAll(".band")
.data(data)
.enter()
.append("g")
.classed("band", true)
.attr("transform", function(d){ return "translate("+[0, y(d.id)]+")"; });
bands.append("rect")
.attr("width", svgWidth)
.attr("height", bandHeight)
.style("fill", function(d){ return (d.stageIndex%2===0)? "#F0F1F3" : "none"; });
bands.append("text")
.classed("delta total", true)
.attr("transform", "translate("+[conceptNameX-15,textY]+")")
.attr("text-anchor", "end")
.text(function(d){ return deltaFormater(d.totalDelta, true); })
.each(textStyler);
bands.append("text")
.classed("concept-name", true)
.attr("transform", "translate("+[deltaNegativeX-15,textY]+")")
.attr("text-anchor", "end")
.text(function(d){ return d.concept; })
.each(textStyler);
bands.filter(function(d,i){ return (i===0 || d.stage!==data[i-1].stage); }).append("text")
.classed("concept-stage", true)
.attr("transform", "translate("+[conceptNameX,textY]+")")
.attr("text-anchor", "start")
.text(function(d,i){ return "["+d.stage+"]"; })
.each(textStyler);
bands.append("text")
.classed("delta negative", true)
.attr("transform", "translate("+[deltaNegativeX+deltaWidth-15,textY]+")")
.attr("text-anchor", "end")
.text(function(d){ return deltaFormater(d.negativeDelta); })
.each(textStyler);
var rects = bands.append("g")
.attr("transform", "translate("+[chartX,0]+")")
rects.append("text")
.attr("transform", function(d){ return "translate("+[x(d.negative)-5,textY+barPaddingTop]+")"; })
.text(function(d){ return labelFormater(Math.abs(d.negative), Math.abs(d.negativePercentage)); })
.attr("text-anchor", "end")
.each(textStyler);
rects.append("rect")
.classed("bar negative", true)
.attr("x", function(d){ return x(d.negative); })
.attr("y", barPaddingTop+barReferenceHeight)
.attr("width", function(d){ return Math.abs(x(d.negative)-x(0)); })
.attr("height", barHeight)
.style("fill", "#CC7174");
rects.append("rect")
.classed("bar positive", true)
.attr("x", function(d){ return x(0); })
.attr("y", barPaddingTop+barReferenceHeight)
.attr("width", function(d){ return x(d.positive)-x(0); })
.attr("height", barHeight)
.style("fill", "#98D9A2");
rects.append("rect")
.classed("bar negative reference", true)
.attr("x", function(d){ return x(d.negativeFrance); })
.attr("y", barPaddingTop)
.attr("width", function(d){ return Math.abs(x(d.negativeFrance)-x(0)); })
.attr("height", barReferenceHeight)
.style("fill", "grey");
rects.append("rect")
.classed("bar positive reference", true)
.attr("x", function(d){ return x(0); })
.attr("y", barPaddingTop)
.attr("width", function(d){ return x(d.positiveFrance)-x(0); })
.attr("height", barReferenceHeight)
.style("fill", "grey");
rects.append("text")
.attr("transform", function(d){ return "translate("+[x(d.positive)+5,textY+barPaddingTop]+")"; })
.text(function(d){ return labelFormater(d.positive, d.positivePercentage); })
.attr("text-anchor", "start")
.each(textStyler);
bands.append("text")
.classed("delta positive", true)
.attr("transform", "translate("+[deltaPositiveX,textY]+")")
.text(function(d){ return deltaFormater(d.positiveDelta); })
.each(textStyler);
//begin: axes
svg.append("path")
.classed("y-axis separator", true)
.attr("d", "M"+[conceptNameX-5, 0]+"v"+svgHeight)
.style("stroke", "lightgrey")
.style("shape-rendering", "crispEdges");
svg.append("path")
.classed("y-axis separator", true)
.attr("d", "M"+[deltaNegativeX, 0]+"v"+svgHeight)
.style("stroke", "lightgrey")
.style("shape-rendering", "crispEdges");
svg.append("path")
.classed("y-axis", true)
.attr("d", "M"+[chartX+x(0), 0]+"v"+svgHeight)
.style("stroke", "grey")
.style("shape-rendering", "crispEdges");
//end: axes
});
/*****************/
/* block utility */
/*****************/
function parser(d, i) {
if (d.concept === 'Total') {
//total row must be the first row
totalCount = d.total;
currentStageIndex = 0;
} else {
//subsequent rows requires totalCount
d.id = d.concept;
d.positive = +d.positive;
d.negative = +d.negative;
d.positiveFrance = +d.positiveFrance
d.negativeFrance = +d.negativeFrance;
d.positivePercentage = d.positive*100/totalCount;
d.negativePercentage = d.negative*100/totalCount;
d.positivePercentageFrance = d.positiveFrance*100/totalCount;
d.negativePercentageFrance = d.negativeFrance*100/totalCount;
d.positiveDelta = d.positivePercentage-d.positivePercentageFrance;
d.negativeDelta = d.negativePercentage-d.negativePercentageFrance;
d.totalDelta = d.positiveDelta - d.negativeDelta;
if (d.stage !== currentStage) {
currentStageIndex += 1;
}
d.stageIndex = currentStageIndex;
min = Math.min(min, d.negative, d.negativeFrance);
max = Math.max(max, d.positive, d.positiveFrance);
data.push(d);
}
currentStage = d.stage;
return d;
}
function deltaFormater(d, forceZero) {
var val = +d.toFixed(1);
if (val===0) {
return forceZero? "0" : "";
} else if (val<0) {
return val;
} else {
return "+"+val;
}
}
function labelFormater(count, percentage) {
if (count===0) {
return "";
} else {
return count + " | " + percentage.toFixed(1) + "%";
}
}
function textStyler() {
d3.select(this)
.style("font-size", 12)
.style("font-family", ["Lucida Grande", "Lucida Sans Unicode", "Arial", "Helvetica", "sans-serif"])
}
</script>
stage concept positive negative positiveFrance negativeFrance occurence total
Total Total 0 0 0 0 0 87
RDV 1- Facilité à joindre la concession/à prendre rdv 0 -1 0 -1 0,4% 1
ACCESSIBILITE 2- Parking client / accès / emplacement 0 -4 1 -4 1,7% 4
ACCESSIBILITE 3- Horaires d'ouverture 0 -1 0 -1 0,4% 1
ACCUEIL 4- Convivialité 56 -1 44 -1 24,9% 57
ACCUEIL 5- Accueil personnalisé 3 0 2 -1 1,3% 3
CONSEIL 6- Personnel disponible 13 0 8 -2 5,7% 13
CONSEIL 7- Ecoute 10 0 10 0 4,4% 10
CONSEIL 8- Compétence du personnel 24 -1 27 -1 10,9% 25
CONSEIL 9- Le personnel fait tout pour me satisfaire 4 0 4 0 1,7% 4
CONSEIL 10- Les informations délivrées sont claires 10 -1 15 -2 4,8% 11
CONSEIL 11- On me laisse le temps 0 0 1 0 0,0% 0
CONSEIL 12- Je suis accompagné tout au long de mon achat 1 -2 5 -3 1,3% 3
CONSEIL 13- Essai 3 0 2 -1 1,3% 3
OFFRE 14- L'offre correspond à ma demande/est claire 5 -2 1 0 3,1% 7
OFFRE 15- Satisfait de la politique tarifaire 3 -2 2 -2 2,2% 5
OFFRE 16- Les démarches administratives conviennent 1 0 0 -1 0,4% 1
ATTENTE LIVRAISON 17- Satisfaction du délai de livraison 4 0 2 -1 1,7% 4
ATTENTE LIVRAISON 18- Respect du délai annoncé 3 0 3 -1 1,3% 3
LIVRAISON 19- Qualité du véhicule livré 1 -1 0 -2 0,9% 2
LIVRAISON 20- Véhicule correspond à la commande 0 0 0 0 0,0% 0
LIVRAISON 21- Qualité de l'évènement de livraison 2 -1 1 0 1,3% 3
LIVRAISON 22- Qualité des explications produit 3 -1 4 -1 1,7% 4
LIVRAISON 23- Cadeaux de livraison 0 0 0 -1 0,0% 0
AUTRES 24- Showroom / Locaux 2 -1 2 -4 1,3% 3
AUTRES 25- Globalement expérience facile/plaisante/rapide 3 0 2 0 1,3% 3
AUTRES 26- Dysfonctionnement technique sur le VE 0 -2 0 -1 0,9% 2
AUTRES 27- SAV 4 -1 1 0 2,2% 5
AUTRES 28- Fidélité (marque/concession/Made in France) 12 0 3 0 5,2% 12
AUTRES 29- Globalement satisfait 24 0 15 0 10,5% 24
AUTRES 30- Globalement très satisfait 16 0 13 0 7,0% 16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment